We’ve been made aware of a fraudulent website impersonating Hyperstack at hyperstack.my. This domain is not affiliated with Hyperstack or NexGen Cloud.
If you’ve been approached or interacted with this site, please contact our team immediately at support@hyperstack.cloud.
DeepSeek-OCR is a multimodal OCR model designed to extract both text and document structure from images and PDFs.
The setup uses a Hyperstack GPU virtual machine to run DeepSeek-OCR in a private, high-performance environment.
The model combines a vision encoder and a language decoder to handle complex layouts such as tables and multi-column documents.
Deployment involves cloning the DeepSeek-OCR repository, installing Python dependencies, and configuring the runtime environment.
A Gradio-based web interface allows users to upload documents and view OCR results in structured Markdown output.
The deployed OCR service can be extended into APIs or integrated into document processing and RAG workflows.
Take Control of Your Own OCR Workflow with DeepSeek-OCR and Hyperstack
Optical Character Recognition (OCR) is the process of recognising and extracting text from a source like images or PDFs using just the visual field - it's what we do when we read!
Methods for performing OCR have exited for a while but in the past few years (or even months rather), transformer-based models have become incredibly competent at it. DeepSeek, one of the world's leading AI foundation model labs, have released their DeepSeek-OCR 3B parameter model for quickly and easily creating your own OCR workflows.
Why is it harder to run than other DeepSeek models?
You might be used to running other AI models, like DeepSeek's LLMs, which are often available via a simple API call or a straightforward Python library like transformers. We've even made tutorials in the past that you can follow to get DeepSeek V3. DeepSeek-OCR is a bit more hands-on because it's not just a language model; it's a specialised multi-modal system.
It essentially has two parts: a sophisticated vision encoder that sees and understands the layout of a page (just like our eyes), and a 3-billion-parameter language decoder that reads and interprets the text from that visual information. This two-stage process is what makes it so powerful, but it also requires a more complex stack of software to run efficiently.
The setup in this guide uses vLLM, a high-throughput serving engine, to get the best possible performance. This is what adds most of the setup steps - we need to install a particular version of it along with dependencies like flash-attn. It's this requirement for a high-performance, GPU-accelerated serving environment that makes it more complex than a simple pip install package, but the payoff in speed and accuracy is well worth it.
How good is DeepSeek-OCR?
In short: it's exceptionally good. It represents the current state-of-the-art for open-source OCR in its size group, especially when it comes to understanding real-world, complex documents.
Where traditional OCR tools might just extract a "wall of text" that loses all formatting, DeepSeek-OCR understands the structure of the document. This is its key advantage. It excels at:
Tables: It doesn't just see text in a table; it understands the table's rows and columns and formats the output (as markdown) to match.
Mixed Content: It's highly adept at handling pages with a mix of text, code blocks, and even mathematical equations.
Because it outputs structured markdown, you're not just getting the raw text; you're getting the document's semantic structure. This makes its output immediately useful for feeding into other systems, like a RAG pipeline or a summarisation model. For its 3B-parameter size, it hits a perfect sweet spot of being incredibly accurate while still being fast enough to interpret huge documents on a single H100 GPU.
How to set up DeepSeek-OCR on your own Hyperstack VM, step-by-step
We'll take you through the whole process from start to end to get a really simple and basic OCR workflow running on your own Hyperstack VM.
Step 0: Getting a Hyperstack VM
This guide assumes you've just spun up a new Linux VM on our platform and can access it via SSH. If you haven't done this before, please see our getting started guide in our documentation.
Step 1: Clone the DeepSeek-OCR repo
# Clone the DeepSeek-OCR repository git clone https://github.com/deepseek-ai/DeepSeek-OCR.git
Step 2: Install UV (the package manager):
curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.local/bin/env
This is a standalone python file that sets up the webserver and hosts it on your VM. We recommend you have a quick read through before you attempt to run it, just to familiarise yourself with what it does (more on this later).
Step 6: Get the code into your VM:
# Create the "web" dir and put main.py in there cd DeepSeek-OCR-master/DeepSeek-OCR-vllm mkdir -p web
cat <<EOF > web/main.py <paste the contents of main.py here> EOF
You can alternatively use some editor like nano or vim, or SSH into this VM from a more interactive source like VSCode or similar to make this part easier.
Step 7: Start the server and access via your browser
# Start the server uvicorn web.main:app --host 0.0.0.0 --port 3000
You should now be able to navigate to the UI by going to http://<your-VMs-ip>:3000, and interact with the UI!
NOTE: Remember to open port 3000 for inbound TCP traffic via your VM's firewall on Hyperstack! For more info on this, see our documentation here
Once loaded, It should look something like this:
In this simple, barebones UI, you can upload PDFs or images and DeepSeek-OCR will automatically run on them.
The results will be visible in the lower box, with the option to see (and download) the labelled input and the extracted text in markdown format.
To re-run, simple delete the existing input and upload something new!
Here's an example of an example PDF article output from DeepSeek-OCR:
Troubleshooting
As stated, this is a very minimal, quickly-put-together UI, and hence is not maintained and updated by Hyperstack, and is certainly not bug-free! However, feel free to modify the code the main.py file to solve any issues or add any features you like.
One bug we are aware of in our early testing is the UI's inability to replace old inputs when new ones are uploaded. In this case, simply Ctrl+C to terminate the server and re-run the same uvicorn command - this and a reload of the web page will then start a fresh instance of the UI with the issue no longer being present.
What's Next?
Congratulations! You've now got your own private, high-performance OCR server running. This Gradio UI is a fantastic sandbox for testing, but the real power comes from what you can build on top of it.
The most logical next step is to adapt the web/main.py file. Instead of launching a Gradio UI, you could modify it to create a simple, robust REST API endpoint using FastAPI. Imagine an endpoint where you can POST an image or PDF file and get a clean JSON response containing the extracted markdown.
Once you have that API, the possibilities are endless:
Build a RAG Pipeline: This is the big one. You can now programmatically feed your entire library of PDFs and documents through this API, storing the clean markdown output in a vector database.
Create a "Chat with your Docs" App: Combine your new OCR API with a conversational LLM (like DeepSeek-LLM) to build a powerful application that lets you ask questions about your documents.
Automate Data Entry: Create a workflow that watches a specific folder or email inbox, runs any new attachments through your OCR API, and then parses the structured output to populate a database or spreadsheet.
You've done the hard part by setting up the core engine. Now you can use your Hyperstack VM as a stable, private microservice to power all kinds of intelligent document-processing workflows.
Launch Your VM today and Get Started with Hyperstack!
FAQs
What type of model is DeepSeek-OCR?
DeepSeek-OCR is a multimodal model combining vision and language understanding, designed to extract text and structure from documents efficiently.
What format does DeepSeek-OCR output?
It outputs structured markdown that preserves tables, layout, and semantic information, making it ready for downstream processing or RAG pipelines.
Which engine is used for high-throughput serving?
vLLM is used as a high-throughput serving engine, optimised for GPU acceleration to deliver fast, efficient OCR performance.
Which package manager is required for setup?
The setup requires UV, a modern package manager, to create virtual environments and install all dependencies reliably on Hyperstack.
Jev: Inside TypeSafe AI's First System One Decision Model
Jev is not a language model in the usual sense. TypeSafe AI ...
Jev is not a language model in the usual sense. TypeSafe AI released the Jev AI model on 15 September 2026 as the first of what it calls a System One model: an engine that takes a state and one or more questions and returns typed answers with probabilities, never a sentence. The company reports 70 to 500 millisecond end-to-end latency, together with headline claims of up to 200 times faster and 400 times cheaper than comparable large language model workflows on the same class of decision.
This piece looks past the announcement. It explains the mechanism that lets Jev skip the sentence entirely, reproduces that mechanism against an open model running on a single Hyperstack NVIDIA GPU, and checks TypeSafe’s own numbers against an independent benchmark that LangChain published within days of launch.
This piece draws on four sources throughout: TypeSafe’s own announcement and documentation, the SGLang documentation behind the scoring mechanism reproduced further down, and LangChain’s own evaluation of Jev against three large language model judges, published as Jev-as-a-Judge for Agent Evals. Figures that come from TypeSafe’s own comparisons are marked as such wherever they appear, separate from the independent measurements.
The Decision Your Code Keeps Paying Full Price For
Most of what software asks a language model to do is not writing. It is a judgement with a small number of possible answers: is this support ticket urgent, which team should own it, is a shell command safe to run, does a retrieved passage answer the question that was asked. None of that needs a paragraph. It needs one of a handful of known labels.
The usual approach reaches for the same tool regardless of the question: a full generative call, a sentence or a JSON object to wait for, and code that parses that answer back into the label it wanted in the first place. Inside an agent loop, that pattern repeats on almost every iteration.
A naive agent loopPYTHON
whilenot task_done:
action = llm(context) # full generation, just to pick a tool
result = run_tool(action)
context += result
task_done = llm(context) # full generation again, just to check "done or not"
Every pass through that loop calls a generative model twice for a judgement with only a handful of possible answers: which tool to call next, and whether the task is finished. Both are single-token decisions, priced and timed as though they were prose.
Two decisions, paid for like paragraphs
A naive agent loop calls a full generative model for a tool choice and a done check, even though both answers are a single label.
The pattern repeats on almost every iteration of the loop, so the cost compounds with every step an agent takes.
The gap between the two ways of answering comes down to how many times the model has to run. Writing an answer means running the model once per output token, because each token depends on the one written immediately before it. A bounded decision needs exactly one pass, because the set of possible answers is already known before the model is ever called.
Cost of Writing T Tokens
cost(T) = T × one forward pass
Decoding is sequential. Token 2 cannot be produced until token 1 has been appended to the sequence, so a T-token answer is T dependent passes through the model, never fewer.
Two ways to answer the same question
One writes a sentence token by token. The other returns three probabilities from a single pass.
The invoice example is illustrative. The mechanism behind the right-hand path is reproduced below against a real model on a Hyperstack NVIDIA GPU.
Jev’s premise, as TypeSafe AI frames it, is that language generation is the wrong interface once code already knows the set of possible answers. If every outcome can be enumerated ahead of time, the system needs a model that scores those outcomes, not one that writes about them.
What Jev Is
TypeSafe’s own documentation defines System One models as a class of AI models built to make fast, structured decisions that software can use directly. A System One model evaluates a state and returns typed answers and probabilities, and it does not write replies, produce code, or explain its own reasoning.
A request to Jev carries two things: a state, which is the text or JSON describing the situation, and one or more questions about that state. Every question declares its answer shape in advance, using one of three primitives.
Choice, Score and Noul
Choice picks one option with a probability for every option. Score places the input on a scale. Noul returns the probability that a yes or no question is true.
Choice selects one option from a list defined by the caller and returns a probability for every option, not only the winner. Score places the input on an ordered scale defined by the caller and returns a probability-weighted position on that scale rather than a single label. Noul, TypeSafe’s own name for the yes-or-no primitive, returns the probability that a stated question is true. Every answer comes back as a number between 0 and 1 that application code can act on directly, with no option outside the ones the caller declared.
A Score answer such as 1.74 on a zero-to-two scale is not a rounding artefact. It is the probability-weighted average across every rung on the scale, so a result close to 2.0 signals near certainty on the top label, while 1.74 signals a strong lean towards the top label with real weight left on the one below it, information a single winning label would discard.
The Score Primitive
score = Σk pk · valuek
The probability of each rung on the scale, multiplied by that rung’s value, summed across every rung. A weighted average, not a single pick.
A single request can carry several questions about the same state, and every one of them is answered from the same evaluation, not a separate call for each.
Jev requestJSON
{
"model": "jev-latest",
"state": "I was charged twice for the same subscription.",
"questions": {
"urgent": {
"type": "noul",
"instructions": "Does this need attention right now?"
},
"team": {
"type": "choice",
"instructions": "Which team should own this ticket?",
"criteria": {
"billing": "Charges, invoices, refunds",
"technical": "Product bugs and errors",
"account": "Login and access problems"
}
}
}
}
The support ticket enters Jev once. Urgency and team ownership are both answered from that same forward pass, and code branches directly off the two probabilities.
The routing code shown is illustrative. Real thresholds should come from evaluation on labelled examples, never a guess.
Application code reads that response directly. There is no prose to parse and no answer outside the declared options, because team can only ever be one of the three names supplied in the request.
How This Differs From Structured Output
It is easy to confuse Jev’s mechanism with structured output, because both restrict what an application receives. The two do different work inside the serving engine.
A structured-output call constrains a model’s generation to a schema, so a request for a ticket’s team might return {"team": "billing"}. The schema prevents an invalid object, but it does not guarantee a correct one. The model still writes that object one token at a time: the opening brace, the field name, the value and the closing brace, decoded exactly as a sentence would be. A schema narrows the shape of the output. It does not remove the decode loop underneath it.
Jev’s fixed-answer scoring exits before that loop starts. Given the same three allowed teams, it reads one score per team from a single forward pass and normalises them into a distribution, with no token of output ever generated.
Two contracts for the same restriction
Structured output stays autoregressive underneath its schema. Fixed-answer scoring reads its answer before the decode loop begins.
Both approaches limit what an application can receive. Only one of them limits how many passes the model has to make.
The same five checkpoints, opposite answers
A generative model and Jev diverge from the first row and never converge again.
Checkpoint
A generative model
Jev
Output
Free-form text, decoded token by token
A typed answer plus a full probability distribution
Sampling
Temperature, top-p and top-k shape an open-ended distribution
A softmax restricted to the declared answers, nothing else
Best fit
Writing, planning, explaining, calling tools
Routing, gating, ranking, classification
Control
Thresholds live inside a prompt, hard to review or test
Thresholds live in application code, reviewable and versioned
Failure mode
Malformed or off-schema prose
A confident, schema-valid, wrong answer
The failure mode row matters most: a schema blocks an answer outside its list, but it never guarantees that the answer inside the list is correct.
Where That Probability Comes From
Seeing why Jev can skip the decode loop starts with what a normal generation step does. A tokenizer converts a prompt into token IDs. The model processes that sequence and produces one vector for the next position, holding a raw score, a logit, for every token in the vocabulary, tens of thousands of numbers for a typical open model.
Turning a logit into a probability means running softmax across every other logit in that whole vocabulary.
Softmax Over the Whole Vocabulary
P(tokeni) = exp(logiti) ÷ Σj ∈ V exp(logitj)
V is the entire vocabulary, tens of thousands of terms in the denominator for a single next-token score.
Ordinary generation does not even run that plain version. It divides every logit by a temperature first, sharpening the distribution below 1 or flattening it above 1, before a token is sampled.
That knob exists because open-ended generation has to decide how much randomness belongs in the answer. A bounded decision never asks that question, so it reports the ratio the raw logits already imply.
That whole cycle, a forward pass, decoding rules, one appended token, repeats until a stop token or a length limit ends it.
The autoregressive decode loop
A forward pass produces one vocabulary-sized vector, decoding rules choose one token, that token is appended, and the cycle repeats until a stop condition ends it.
Every one of these passes depends on the token the previous pass just wrote, which is why they run one at a time rather than together.
Reading Three Logits Instead of Tens of Thousands
A bounded decision only needs the very first vector, and only a handful of its entries. Labelling three answers A, B and C inside the prompt, and ending that prompt exactly where the model would normally start writing the label, puts the logits for all three at a single, known position. The model still produces its usual vocabulary-sized vector for that position. It holds the logit for A, the logits for B and C, and every other token in the vocabulary besides.
Every logit outside the three declared labels is discarded, and softmax runs only across the three that remain.
Only the denominator changes: a sum over three declared answers in place of a sum over the whole vocabulary.
From tens of thousands of logits to three probabilities
Every logit except the three declared labels is discarded before softmax ever runs.
With logits of 6.8, 4.3 and 3.1 for A, B and C, the restricted softmax gives roughly 0.90, 0.07 and 0.02. That is not a claim that billing has 90 per cent probability across the entire vocabulary. It is how the model splits its preference among exactly the three answers the application allowed, after every other token has been ruled out.
Why the Labels Are Single Letters, Not Whole Words
A visible word is not necessarily one token. billing might be one token for one tokenizer and several for another, and technical support spans more than one position under any tokenizer. Comparing multi-token phrases would need sequence scoring: score the first token, append it, score the next, and combine the values for the whole phrase, with length becoming part of the comparison. A single-letter label avoids that problem entirely, because every option sits at exactly one output position while the meaning behind it still lives in the prompt next to the letter.
A second trap sits underneath the first. Tokenizers often fold a leading space into a token, so A and A can resolve to two different token IDs, and a chat template can insert that whitespace without it being obvious from the prompt text alone. The way around this is to render the full prompt through the model’s own chat template, check the exact continuation it expects at the answer position, and confirm through a tokenizer call that each label resolves to exactly one token before trusting a scoring request against it.
Two reasons the labels are single verified letters
A visible word can span more than one token, and a leading space can silently point at a different token from the one intended.
Both failure modes are silent: nothing raises an error, the scoring call reads the wrong position without complaint.
📘
The declared answers still need an escape route. Restricted softmax always assigns every unit of probability mass to one of the declared labels, even when none of them fit. A security incident landing on a router that only knows billing, technical and account still gets confidently filed as one of those three. Adding an OTHER or ESCALATE option whenever the declared list might not be exhaustive is cheap insurance against exactly that failure.
Reproducing the Mechanism on a Hyperstack NVIDIA GPU
Jev itself is closed. Its weights, its Reinforcement Learning for Calibrated Decisions training run and its evaluation stack are not published. The restricted-softmax mechanism behind it, however, already sits inside open serving engines, which means the trick can be built and measured against a model under full control rather than taken on trust. The steps below reproduce that mechanism using SGLang, run here on a single Hyperstack NVIDIA H100 virtual machine rather than a laptop.
SGLang is the same serving engine behind the Kimi K3, the Qwen3.8 Max and the MiniMax H3 tutorials, so a virtual machine already configured for one of those deployments needs no extra setup for this exercise. A single NVIDIA H100 is more GPU than a 0.5 billion parameter model needs, which is the point: the model here is small on purpose, chosen so the mechanism, not the hardware, is what gets measured.
decide.py, SGLang and the model, on one GPU
The client tokenizes nothing itself. SGLang tokenizes the labels, runs the model once, and returns the three probabilities.
Every request below runs against a single Hyperstack NVIDIA GPU virtual machine. No cluster, no multi-node networking, and no tensor parallelism are needed for a model this size.
Starting the Model Server
A clean environment needs two packages: SGLang itself, and requests for the client that follows.
The first launch downloads Qwen2.5-0.5B-Instruct from Hugging Face. Later launches reuse the local cache, and the model stays resident in GPU memory on port 30000 for every request that follows.
Defining the Choices and Building the Prompt
The client, decide.py, starts with the labels and the ticket being routed. The dictionary keeps both representations of every answer together, the letter that gets scored and the meaning returned to the application, so their order cannot drift apart between tokenizing, scoring and mapping the result back.
decide.pyPYTHON
# decide.pyimport json
import requests
BASE_URL = "http://127.0.0.1:30000"
MODEL = "Qwen/Qwen2.5-0.5B-Instruct"# A is the token that actually gets scored. "billing and payments" is what# the application does with the answer once it comes back.
choices = {
"A": "billing and payments",
"B": "technical support",
"C": "account access",
}
ticket = "I was charged twice for the same subscription."
decide.py, continuedPYTHON
# decide.py (continued)
choice_lines = "\n".join(
f"{label} = {meaning}"for label, meaning in choices.items()
)
prompt = f"""Ticket:
{ticket}
Question:
Which category matches the ticket?
Allowed labels:
{choice_lines}
Return only the label.
Label:
"""print(prompt)
Printed promptOUTPUT
Ticket:
I was charged twice for the same subscription.
Question:
Which category matches the ticket?
Allowed labels:
A = billing and payments
B = technical support
C = account access
Return only the label.
Label:
The prompt ends at Label:, the exact position whose vocabulary vector gets read. SGLang is never asked to generate the token that would normally follow it.
Resolving the Label Token IDs
Before anything can be scored, the real token ID behind each of A, B and C has to be confirmed against this specific tokenizer, not assumed from any other model.
decide.py, continuedPYTHON
# decide.py (continued)
label_token_ids = []
for label in choices:
response = requests.post(
f"{BASE_URL}/tokenize",
json={"model": MODEL, "prompt": label, "add_special_tokens": False},
timeout=30,
)
response.raise_for_status()
token_ids = response.json()["tokens"]
iflen(token_ids) != 1:
raise ValueError(f"{label!r} is not a single token: {token_ids}")
print(f"{label!r} -> {token_ids}")
label_token_ids.append(token_ids[0])
Resolved token IDsOUTPUT
'A' -> [32]
'B' -> [33]
'C' -> [34]
Each label returns a single integer, so every option occupies exactly one vocabulary position. Had any label produced two or more IDs, the raise ValueError above would have stopped the script there rather than let a silent mismatch reach the scoring call.
Scoring the Three Positions
With the token IDs confirmed, the decision itself is a single HTTP call to SGLang's /v1/score endpoint, documented as computing token probabilities for specified tokens given a query, intended for classification tasks, scoring responses and log-probability computation.
query carries the full prompt. The empty string inside items tells SGLang to score the position immediately after it. label_token_ids names the three positions to read, and apply_softmax normalises those three readings into a proper probability distribution. SGLang tokenized the prompt, ran the model once, and returned three numbers in the same order the token IDs were sent, with no token generated to produce them.
The Real Numbers From This Run
logits 25.2776, 24.4982, 21.1888 → P = 0.678, 0.311, 0.011
The raw logits behind this call, before apply_softmax normalised them, plugged into the restricted softmax from the mechanism above by hand, land on the same 0.678 SGLang printed.
Mapping the Scores Back to a Decision
The last step is ordinary code, turning three floats back into the labels the application understands.
{
"decision": "billing and payments",
"probabilities": {
"billing and payments": 0.6777623295783997,
"technical support": 0.31087860465049744,
"account access": 0.011359035037457943
}
}
Billing wins, but only at 0.678, well short of the 0.9-plus a reader might expect. A threshold of 0.70 would catch exactly this case and send it for a second look rather than route it without review, which is the kind of decision that belongs in application code rather than inside the model.
Measuring the Difference Against Standard Generation
The mechanism explains why scoring should be faster. Measuring it is more convincing than assuming it. The scoring path above was run against a standard chat-completion call on the same SGLang server, the same model, and the same 100 support-ticket cases, both starting at the same instant. Standard generation decoded up to 32 tokens per answer, an explanation alongside the label.
Measured on the same NVIDIA GPU, the same server, the same requests
Average time per decision across 100 identical cases.
A measured 1.83 times gap, independent of any vendor comparison, on identical hardware and identical requests.
SCORING
463 ms
average time per decision, one forward pass
GENERATION
848 ms
average time per decision, up to 32 decoded tokens
MEASURED GAP
1.83x
on the same NVIDIA GPU, same server, same requests
That gap is also a cost figure. Every millisecond a request holds a GPU is a millisecond another request cannot use it, so the same measurement that explains Jev’s latency claim also explains why a workload built on this pattern needs less GPU time per decision than one built on ordinary chat completions, whether the model behind it is Jev or an open one served from a Hyperstack GPU billed by the minute.
Why the Number Next to the Answer Matters
A typed answer solves half the problem. The label tells application code what won. The probability tells it how close the race was, and that second number is what the code should branch on.
Routing that ticket without a second look would be reckless. Billing won, but barely, and a low-confidence win should never take the same branch as a decisive one. One workable definition of confidence is the margin between the top answer and its closest rival, rather than the winning probability taken alone.
Confidence as a Margin
confidence = p(1) − p(2)
Billing at 0.52 against technical at 0.46 gives a margin of 0.06, close to a coin flip. A 0.52 winner and a 0.91 winner should never take the same branch, even though both technically won.
The same headline hides two different shapes
A confident 0.91 win and a barely-ahead 0.52 win route to different branches: automate, escalate to a larger model, or send to a person.
The thresholds live in code, where they can be reviewed, tested and changed. A dashboard label might tolerate a shaky prediction. A command that deletes data should demand a far higher bar.
TypeSafe trains Jev with what it calls Reinforcement Learning for Calibrated Decisions, RLCD for short. The target is calibration, not only accuracy: a batch of answers each carrying 90 per cent confidence should turn out correct on roughly 90 per cent of those cases. Measuring that means binning every prediction by its stated confidence and comparing each bin’s average confidence against how often that bin was right.
A model can be accurate on average while still being miscalibrated, confidently wrong in exactly the band a reader would otherwise trust most. Confidence that does not track correctness is decoration, not a signal code can threshold on, and checking it needs a labelled evaluation set.
The Hallucination Claim, Examined Closely
TypeSafe says Jev cannot hallucinate. That statement holds under a narrow definition, and it is worth separating from a broader one that it does not support.
Declaring billing, technical and account as the only valid answers means the response can never come back as legal, an option that does not exist in the schema. It also cannot return malformed prose where code expected a label. That part of the hallucination problem does not apply to it.
It can still confidently choose the wrong valid option. A login bug scored as billing instead of technical is schema-valid and still wrong, and now the wrong customer gets refunded over an issue that was never about a charge. A schema rules out an answer that does not exist. It says nothing about whether the answer that does exist is the right one.
Two different guarantees
A schema blocks an answer outside its declared list. It has no mechanism for catching a wrong answer that sits inside that list.
A more precise sentence than "Jev cannot hallucinate" is that it cannot break its declared output schema, but it can still be wrong.
Schema Membership Against Correctness
P(answer ∈ schema) = 1, P(answer correct) ≤ 1
Membership in the declared schema is guaranteed with certainty. Correctness never comes with the same guarantee, and the two should not be spoken about as though they were one property.
Where Jev Earns Its Place Inside an Agent
As a System One model, Jev works best alongside a generative model, not instead of one. The generative model plans, writes, explains and calls tools, the work that needs language. Jev handles the frequent small decisions that surround that work.
Three checkpoints inside an ordinary agent loop
Jev sits beside the loop at three points: choosing which model should answer, gating a risky tool call, and verifying the result before the loop continues.
None of these checkpoints replace the generative model. Each one decides something about the work around it.
Model Routing
A simple lookup does not need the same model as a demanding architecture review. Jev can score the incoming request and select the least expensive model likely to complete it. The router decides which model should answer, not the answer itself.
Tool Risk Gating
Before an agent runs a shell command, Jev can classify it as read-only, reversible or destructive, alongside separate questions about whether it touches production or leaves an approved directory. A high-confidence read-only command can continue unattended. A destructive or uncertain one pauses for a person.
One command, one Choice call, three consequences
The risk class Jev returns decides whether the agent continues, continues with a logged call, or pauses for review.
{
"model": "jev-latest",
"state": "rm -rf /data/tmp",
"questions": {
"risk": {
"type": "choice",
"instructions": "Classify this shell command.",
"criteria": {
"read_only": "Inspects state, changes nothing",
"reversible": "Changes state, but the change can be undone",
"destructive": "Deletes data or is otherwise hard to undo"
}
}
}
}
Verification
An agent can report that a task is finished while its tests are still failing. Jev can inspect the state and answer bounded questions: did the tests pass, is the agent repeating the same failed action, does the output follow policy. A hard test still catches what it was built to catch. Jev adds a semantic check for what that hard rule was never designed to see.
Problems Jev Can Solve Today
Jev sits within a broader set of AI models for automation, but it fits a specific niche inside that set. The workloads where it earns its keep share three properties: every possible answer can be named ahead of time, a careful person could judge the input quickly by hand, and the decision happens often enough that latency or cost start to matter.
Five places the same typed-decision pattern keeps paying off
Support and operations, search and retrieval, quality and safety, high-volume classification, and real-time interfaces.
Category
Where it helps
Support and operations
Classifying intent, urgency, department and frustration in one pass. Routing refunds through several small checks rather than one large prompt. Ranking incidents by severity before a person reads them.
Search and retrieval
Reranking passages by whether they answer the query, not only by semantic closeness. Checking whether a citation supports a claim. Filtering irrelevant chunks before they reach an expensive model.
Quality and safety
Screening prompts for jailbreak attempts or injection. Checking generated content against a written policy. Flagging a risky code change or tool call before it runs, beside deterministic checks rather than in place of them.
High-volume classification
Labelling documents or customer messages at a scale that was previously too costly to afford. Turning free text into features for a traditional machine-learning model. Scoring every row of a corpus against the same rubric.
Real-time interfaces
Choosing the next browser action from a known set of page elements. Scoring tone while someone is still typing. Jev is text-only today, so any of this needs the environment turned into text first.
A plain if statement still beats any model when deterministic code already solves the problem correctly. It is faster, cheaper and easier to test than a call of any kind.
Does This Hold Up? An Independent Benchmark
TypeSafe’s own numbers for the Jev AI model deserve a careful reading. A company benchmarking its own product tends to choose the comparison that flatters it, and TypeSafe AI reports 70 to 500 millisecond latency, $0.042 per million input tokens with output free, and headline claims of up to 200 times faster and 400 times cheaper than comparable large language model workflows. Those figures are a ceiling worth treating with care, not a promise for every workload, even where the underlying mechanism behind them is real and measured earlier in this piece.
A comparison run by a party with nothing to gain from Jev looking good is worth more. LangChain ran exactly that kind of test, comparing Jev against three large language model judges, GPT-5.6 Luna, GPT-5.6 Terra and Claude Sonnet 4.6, using its own Deep Agents harness and LangSmith for the dataset. A weather agent answered five fixed requests, each response was captured once, and every judge scored the same frozen traces across 100 repetitions, so differences in the results measure the judge rather than the agent.
Across 500 repeated pass or fail judgements against a human oracle, Jev matched the human on every single one. GPT-5.6 Terra matched on 99.8 per cent, GPT-5.6 Luna on 96.4 per cent, and Claude Sonnet 4.6 on 80.0 per cent.
Agreement with a human oracle, 500 repeated decisions
Jev matched the human reviewer on every repeated judgement. The three large language model judges did not.
Source: LangChain, Jev-as-a-Judge for Agent Evals.
Accuracy alone tells half the story, since a judge can be right on average while swinging between different answers on the exact same input from one run to the next. Measuring variance across 100 repetitions on a single frozen case is where that gap opens further.
Variance Across Repeated Scores
Var(x) = E[(x − μ)2]
The mean squared distance of each repeated score from the average score on the same, unchanged case.
Quality-score variance on identical, frozen cases
Jev’s mean variance was 0.0000149. The three large language model judges were 92 to 913 times higher on the same cases.
Lower variance does not by itself prove a judge is right. It can be consistently wrong. Once a judge is accurate, though, low variance is what turns that accuracy into something worth automating.
Cost and latency followed the same pattern. Jev averaged $0.00035 and 0.44 seconds per call. GPT-5.6 Luna averaged $0.00039 and 2.50 seconds, GPT-5.6 Terra $0.00289 and 2.83 seconds, and Claude Sonnet 4.6 $0.02811 and 2.16 seconds. The full project cost $0.34 for Jev against $28.17 for Claude Sonnet 4.6.
Cost per evaluator call
Log scale, because the range spans roughly 80 times between the cheapest and most expensive judge.
Source: LangChain, Jev-as-a-Judge for Agent Evals.
Latency per evaluator call
Jev finished in under half a second on average. Every large language model judge took more than two seconds.
Source: LangChain, Jev-as-a-Judge for Agent Evals.
LangChain combined accuracy and consistency into a single signal value: oracle agreement multiplied by repeatability, the chance that two independent calls on the same frozen trace reach the same verdict.
Signal Value
signal = accuracy × repeatability
A judge that is right only sometimes, or repeatable only while wrong, scores close to zero under this definition. Only a judge that is both correct and consistent scores well.
Signal value and what it costs to run at scale
Signal value is accuracy multiplied by repeatability. The 30-day figure projects the measured per-call cost across 10,000 evaluated traces a day.
Judge
Signal value
30-day cost, 10,000 traces a day
Jev
100.0%
$103.59
GPT-5.6 Luna
90.1%
$117.24
GPT-5.6 Terra
99.4%
$866.61
Claude Sonnet 4.6
80.0%
$8,434.08
Source: LangChain, Jev-as-a-Judge for Agent Evals.
30-day projected cost at 10,000 traces a day
Log scale. The gap between Jev and Claude Sonnet 4.6 is roughly 81 times at this volume.
A judge this cheap changes what teams can afford to evaluate, not only how much a single call costs.
One agent, five test cases and a narrow evaluation slice is not the final word on every workload. It is, however, an independent measurement rather than a vendor’s own comparison, and it points in the same direction the mechanism itself predicts.
Where Jev Falls Apart
Jev becomes less useful the moment the answer space stops being known ahead of time.
It cannot write a response, summarise a document, generate code, or explain its own reasoning.
It is unreliable at arithmetic, counting, date comparison or exact string manipulation. Those belong in ordinary code.
It struggles when a decision needs several hidden reasoning steps chained together. Splitting the judgement into smaller questions, or using a reasoning model, works better.
It cannot extract an unknown value directly out of text. Finding candidate values first with something else, then letting Jev choose among them, is the workable pattern.
Irrelevant context can reduce its accuracy, so only the state a decision needs should be sent.
Closed weights, an early access window, text-only input and still-thin independent calibration data outside the one benchmark cited above mean it is early to place full trust in a decision with real consequences.
Where each approach fits
Jev suits known, frequent decisions. A generative model suits open-ended work. A plain if-statement still beats either for a known answer that is rarely asked.
A bounded, high-volume decision is Jev’s sweet spot. Open-ended work still belongs to a generative model, and a rare bounded decision belongs to ordinary code.
A simpler rule sits underneath all of this. If deterministic code already solves the problem correctly, the code should stay. A plain if statement is faster, cheaper and easier to test than any model.
A Sane Way to Roll This Out
A cheap model can still turn out expensive if its mistakes generate retries, manual review or an incident, so a rollout has to measure the whole workflow, not the token price on its own.
Choose one bounded, low-risk decision. A small, clearly named set of possible answers, not the riskiest step in the pipeline.
Write the rubric before calling the model. Decide in words what belongs in every option. The rubric is part of the system, not an afterthought added later.
Collect representative examples first. Include the ambiguous and adversarial cases on purpose.
Run it in shadow mode. Let it answer every real case without changing behaviour, and log its probabilities beside the current outcome.
Plot accuracy against confidence and set thresholds from that data. The cut-off should come from the shadow-mode numbers, never a guess.
Automate the safest branch first. Keep a person, or a stronger model, as the fallback for anything the thresholds flag as uncertain.
Version everything. Pin the model version, the questions, the criteria and the thresholds together, so a change can be replayed against the same evaluation set.
Four stages of increasing trust
One evaluation set feeds all four stages, and human or stronger-model review stays available even at the last one.
Shadow mode never changes behaviour on its own. Every stage after it automates a little more of the safest branch, never all of it at once.
Threshold Selection
θ* = argmaxθ accuracy(θ) subject to coverage(θ) ≥ cmin
The threshold that maximises accuracy while still automating at least the minimum volume the business needs, not the highest bar in the abstract.
Raising the bar too far means the system never makes a mistake because it never decides anything, which is not automation. It is expensive logging. The coverage constraint above keeps that from happening.
Why Build Decision Engines on Hyperstack?
Hyperstackis a cloud platform built for AI andmachine learningworkloads, from large-scale training down to AI models for developers prototyping on a single GPU, the way the walkthrough above did. A typed decision engine, whether it is Jev itself or the open pattern reproduced above, still sits beside a generative model that needs its own GPU. Here is what that pairing needs from a provider, and how it maps onto the platform:
One NVIDIA GPU Is Enough to Start The mechanism above ran on a single NVIDIA H100. Prototyping a scoring pattern needs no cluster, only one machine to prove the branch is worth automating.
Per-Minute Billing for Fast Iteration Spot virtual machines, billed by the minute, make shadow mode and threshold tuning cheap to repeat rather than a reason to skip it.
The Same SGLang Image, Already Proven The Kimi K3 and Qwen3.8 Max deployments already run SGLang on Hyperstack, so a scoring endpoint and a serving endpoint can share one known-good image.
A Larger NVIDIA GPU When the Generative Side Grows The decision engine stays small. The model it routes towards often does not. NVIDIA H200 SXM is one step away on the same platform when that model needs more memory.
Firewalled Environments for a Scoring Endpoint Firewall rules attach per machine inside an environment, so an internal decision endpoint stays reachable only from the services meant to call it.
Transparent Rates While Thresholds Are Being Tuned The Hyperstack GPU pricing page lists every rate per GPU per hour up front, so the cost of a shadow-mode week is known before it is booked.
Build the scoring pattern on your own NVIDIA GPU
Try a Jev-Style Decision Engine on Hyperstack
One NVIDIA H100, SGLang, and an open model are enough to measure the pattern behind Jev for yourself, on the same platform already serving Kimi K3, Qwen3.8 Max and MiniMax H3.
One NVIDIA H100 GPUSGLang /v1/score463 ms measuredBilled by the minute
Launch an NVIDIA GPU virtual machine on Hyperstack today.
FAQs
What is Jev?
The Jev AI model is TypeSafe AI’s first System One model, released 15 September 2026. As a probabilistic AI model, it takes a state and one or more typed questions and returns typed answers with probabilities in a single forward pass, rather than generating text, and cannot write prose, code or explanations.
Can Jev’s scoring mechanism be reproduced without TypeSafe’s API?
Yes. SGLang’s /v1/score endpoint exposes the same restricted-softmax mechanism used by Jev against any open model, so a Choice or Noul-style answer can be computed from one forward pass on a Hyperstack NVIDIA GPU, with no access to Jev’s own weights required.
How is Jev different from structured output?
Structured output constrains a class of AI models for structured outputs to a schema, but generation still happens token by token underneath it. Jev’s fixed-answer scoring reads three or more logits from a single forward pass instead, and never enters the decode loop, so no token is ever generated.
How much GPU do you need to reproduce this pattern?
A single NVIDIA GPU is enough for a small model such as Qwen2.5-0.5B-Instruct, the model used in the walkthrough above. Production workloads with larger models still need only one forward pass per decision, so the sizing question is model size, not decision volume.
Does Jev replace LLM-as-judge evaluation?
Not entirely. Jev belongs to a small set of LLM alternatives for automation built specifically for bounded decisions, and LangChain’s benchmark found it more accurate and consistent than three LLM judges on a narrow agent-evaluation task, at a fraction of the cost. Open-ended judgements that need written reasoning still belong to a generative model, not a typed decision engine.
Deploying Qwen3.8 Max: A Guide to Multi-Node GPU Cloud Inference
Qwen3.8 Max is a 2.4 trillion parameter mixture-of-experts ...
Qwen3.8 Max is a 2.4 trillion parameter mixture-of-experts model from the Qwen team, and 95 billion of those parameters are awake for any one token. Alibaba put the hosted version in front of developers on 3 August 2026 and published the weights shortly afterwards, which is the first time a Qwen Max class model has been released for anyone to run on their own hardware. The FP8 checkpoint is 2.50 TB across 213 safetensors shards, so a Qwen3.8 Max deployment is a multi-node exercise before it is anything else.
This guide deploys it on Hyperstack across eight nodes of eight NVIDIA H100 80GB PCIe GPUs: eight virtual machines, 64 GPUs in total, running SGLang with tensor parallel 16 by pipeline parallel 4. Every figure below comes from one session on 12 August 2026 that pulled 2.50 TB of weights, reached a live OpenAI-compatible endpoint in under six minutes of engine start-up, and then answered chat, reasoning, tool-calling and concurrency workloads with zero failed requests across the sweep.
There is one thing worth settling before the hardware. The hosted Qwen3.8 Max API does not publish an activated parameter count, which leaves anyone modelling cost to guess at the compute behind a token. The open checkpoint states it in config.json: 95 billion active, ten routed experts of 512 plus one shared expert, 92 layers. That is what makes a Qwen3.8 Max deployment something you can size and price rather than estimate. If you have followed our Kimi K3 multi-node deployment, this is the same class of exercise with a different bottleneck, and the reasons are arithmetic rather than memory.
Qwen3.8 Max is a sparse model with a hybrid attention stack. Both halves of that sentence change how you deploy it. Take them one at a time before booking any hardware.
The Qwen3.8 Max FP8 checkpoint
The numbers that decide the cluster, taken from the published model card and the config.json inside the FP8 repository.
2.4T total parameters95B active512 experts10 routed plus 1 shared92 layersGated DeltaNet plus gated attentionBlock FP8 128 by 128262,144 token context
CHECKPOINT
2.50 TB
213 safetensors shards in the FP8 repository
QUANTISATION
Block FP8
fine-grained, block size 128 by 128, dynamic activation scaling
LAYERS
92
69 Gated DeltaNet and 23 gated full attention, one in four
HIDDEN SIZE
8,192
head dimension 256, 64 query heads, 4 key-value heads
EXPERTS
512 per layer
ten routed and one shared awake per token
CONTEXT
262,144 tokens
native, and documented as extensible to 1,010,000
Sources: the Qwen3.8-2.4T-A95B-FP8 and the Qwen3.8-2.4T-A95B model cards. The licence on both repositories is named qwen3.8-max, which is the clearest signal that the open weights and the hosted flagship are the same model.
Sparsity Is What Makes 2.4 Trillion Parameters Servable
Every parameter has to be resident in GPU memory, because the router picks its experts per token and per layer, and there is no useful way to predict which ones. What sparsity buys is compute rather than capacity. The forward pass touches 95 billion parameters, so the cluster is sized by the weight of the model and the speed is set by a far smaller number.
What one token wakes up
The router selects ten of 512 experts in each layer, and one shared expert always runs alongside them.
Drawn from the layer configuration in the FP8 config.json: num_experts 512, num_experts_per_tok 10, plus a shared expert of intermediate size 2,048.
Resident against active
The full model has to fit. Only a small slice of it does the work on any given token.
Computed from the published parameter counts: 95 billion of 2,400 billion is 3.96 per cent, and eleven experts of 512 is 2.15 per cent.
The Hybrid Attention Stack Sets the Concurrency Limit
The 92 layers are not uniform. Three out of every four use Gated DeltaNet, a linear attention layer that carries a fixed-size recurrent state per sequence. The fourth is gated full attention with a conventional key-value cache. The configuration expresses this as full_attention_interval: 4, which gives 69 linear layers and 23 full attention layers.
One block, repeated 23 times
Three Gated DeltaNet layers, then one gated full attention layer, all the way to 92.
Layer types read directly from the layer_types array in config.json: 69 entries of linear_attention and 23 of full_attention.
The practical consequence arrives at serving time. A conventional transformer runs out of concurrency when the key-value cache fills, so the limit moves with context length. Here the recurrent state is a fixed cost per sequence regardless of how long that sequence becomes, and it is the larger of the two pools. When the engine reported its capacity, the ceiling on concurrent requests came from the recurrent state pool rather than from the key-value cache, and that shapes the whole throughput picture further down.
What the Published Scores Say
Qwen positions this release around long-horizon agentic work, coding and research rather than raw single-turn question answering, and the published headline scores follow that emphasis.
Qwen3.8 Max, published benchmark scores
Three results quoted on the model card, spanning research work, terminal use and software engineering. Higher is better on each.
Scores as published on the Qwen3.8-2.4T-A95B model card, quoted for context on what the model is aimed at rather than reproduced here.
📘
Two repositories, one model. The BF16 checkpoint is roughly 4.45 TiB and the FP8 checkpoint is 2.27 TiB, which the vLLM recipe reports as needing three to six nodes and two to four nodes respectively. This deployment uses FP8, and the quantisation is native to the checkpoint rather than applied at load time.
The Qwen3.8 Max Deployment Architecture: Why 64 NVIDIA H100 GPUs
Sizing a cluster for a 2.50 TB checkpoint looks like a division problem and is not one. Memory rules out the small clusters, and then a second constraint rules out two more that memory was perfectly happy with. Getting to the right answer takes both filters, in that order.
The checkpoint against the cluster
Eight nodes of eight NVIDIA H100 80GB cards give 5,120 GB of GPU memory. The weights take a little under half of it.
Checkpoint size from the Qwen3.8-2.4T-A95B-FP8 repository: 213 shards totalling 2.50 TB.
The First Filter Is Memory, and It Is the Forgiving One
Weights are only part of what a GPU has to hold. The recurrent state pool, the key-value cache and the captured CUDA graphs all come out of the same 80 GB, and whatever is left is what serves the running batch. We sized this deployment against a floor of roughly 25 GB free per GPU after weights, which turns the question from a theoretical lower bound into a usable one.
Four nodes fail this outright. The weights alone would need 78.1 GB of an 80 GB card, which leaves nothing for anything else. Five nodes clear the weights and then fall under the free-memory floor. Six and seven nodes pass comfortably.
The Second Filter Is Divisibility, and It Is the Strict One
A parallel placement is only legal if the numbers divide. The pipeline degree has to divide the layer count evenly, and the tensor degree has to divide the attention head count and the expert count. Applying that to six and seven nodes removes both, even though both fit the weights with room to spare.
Memory headroom is not a feasibility test
Two cluster sizes hold the weights comfortably and still cannot be configured at all.
Independently re-derived by the model itself during the tool-calling run further down, which rejected six and seven nodes for the same reason.
The arithmetic behind that is short enough to check by hand. Qwen3.8 Max has 92 layers, and 92 factorises as 2 squared times 23, so a pipeline degree has to be one of 1, 2, 4, 23, 46 or 92. It also has to divide the GPU count. On 48 GPUs that leaves pipeline degrees of 1, 2 and 4, giving tensor degrees of 48, 24 and 12. None of those divides 64 query heads. On 56 GPUs the tensor degrees are 56, 28 and 14, and again none divides 64. Six and seven node clusters are not tight, they are impossible.
Every count that has to divide, at tensor parallel 16
The chosen placement splits each of these cleanly, which is what makes it legal.
What is being split
Value in the config
Divided by 16
Result
Query heads
64
64 / 16
4 per GPU
Gated DeltaNet key heads
16
16 / 16
1 per GPU
Gated DeltaNet value heads
128
128 / 16
8 per GPU
Routed experts
512
512 / 16
32 per GPU
Hidden size
8,192
8,192 / 16
512 per GPU
Layers, across pipeline stages
92
92 / 4
23 per stage
Values read from config.json in the FP8 repository. Key-value heads are 4, which is smaller than the tensor degree, so the engine replicates them across the group. That is ordinary behaviour for grouped-query attention at high tensor parallelism, and it means the key-value cache does not shrink as the tensor degree rises.
Why Pipeline Parallel 4 and Tensor Parallel 16
Three placements are legal on 64 GPUs: tensor 64 by pipeline 1, tensor 32 by pipeline 2, and tensor 16 by pipeline 4. The choice between them is about where the traffic goes.
A tensor-parallel group communicates on every layer, so its members want to be as close together as possible. At tensor 64 the group spans all eight machines, and every collective in every layer crosses the network across 64 ranks. At tensor 16 the group spans two machines, and the four pipeline stages exchange activations only at the three stage boundaries. Twenty-three layers per stage divides evenly. The SGLang cookbook lists a verified four-node NVIDIA H200 placement for this same FP8 checkpoint, which is the same weights on cards large enough to need half as many GPUs. Our guide to tensor and pipeline parallelism covers the general trade-off between the two axes if you want the background.
Tensor parallel 16 by pipeline parallel 4, over eight nodes
Each pipeline stage owns 23 layers and runs across two machines. Every GPU in the cluster holds a slice of every expert it is responsible for.
STAGE 0
23 layers
0 to 22
NODE 0
01234567
NODE 1
01234567
TP 16
▼
STAGE 1
23 layers
23 to 45
NODE 2
01234567
NODE 3
01234567
TP 16
▼
STAGE 2
23 layers
46 to 68
NODE 4
01234567
NODE 5
01234567
TP 16
▼
STAGE 3
23 layers
69 to 91
NODE 6
01234567
NODE 7
01234567
TP 16
NODES
8
GPUS
64
LAYERS PER STAGE
23
PER TENSOR GROUP
16 GPUs
The exact shape launched in this deployment: --tp-size 16 --pp-size 4 --nnodes 8.
💡
Pipeline parallelism turns off speculative decoding. The config declares mtp_num_hidden_layers: 1, so a multi-token prediction head exists in the checkpoint. With a pipeline degree above one the engine does not use it, and the startup log states plainly that pipeline parallelism is incompatible with the overlap schedule. On hardware where the whole model fits in one tensor group, that head becomes available again.
What the Fabric Decides About Your Launch Flags
The launch command carries four settings that exist because of how the GPUs are wired, not because of anything about Qwen3.8 Max. Reading the topology first makes all four obvious.
nvidia-smi topo -m, one node, abridgedOUTPUT
GPU0 GPU1 GPU2 GPU3 GPU4 GPU5 GPU6 GPU7 NIC0
GPU0 X NV12 PHB PHB PHB PHB PHB PHB PHB
GPU1 NV12 X PHB PHB PHB PHB PHB PHB PHB
GPU2 PHB PHB X NV12 PHB PHB PHB PHB PHB
GPU3 PHB PHB NV12 X PHB PHB PHB PHB PHB
...
NIC0 PHB PHB PHB PHB PHB PHB PHB PHB X
NV# = Connection traversing a bonded set of # NVLinks
PHB = Connection traversing PCIe as well as a PCIe Host Bridge
How the cards inside one node are joined
NVLink bridges pair the cards. Every other path between them runs through the PCIe host bridge.
The NVIDIA H100 80GB PCIe flavour carries three NVLink bridges per card at 600 GB/s bidirectional, which is what nvidia-smi reports as NV12 between paired devices.
A tensor-parallel group of 16 covers eight cards on one machine and eight on another, so the group is a multi-node group by definition. Two single-node optimisations then switch themselves off, and the engine says so during startup.
Startup, the collective path being chosenOUTPUT
[18:15:14 PP0 TP0] Init torch distributed begin.
[18:15:40 PP0 TP0] CustomAllreduce is disabled because this process group
spans across nodes.
[18:15:40 PP0 TP0] Init torch distributed ends. elapsed=26.60 s, mem usage=0.47 GB
[18:15:46 PP0 TP0] multimem all-gather disabled because the TP group spans
across nodes.
Neither line calls for action. Both are the engine selecting the portable path because the fast path is only correct within one machine. What does call for action is telling the collectives which interface to use, because a multi-homed node will otherwise pick the wrong one and the rendezvous will not complete.
The four fabric settings, and why each one is there
These belong in the container environment rather than in the server arguments.
Setting
Value used
Why
NCCL_SOCKET_IFNAME
ens6
Names the interface holding the private 10.x address, so collectives use the private network rather than the public one.
GLOO_SOCKET_IFNAME
ens6
The rendezvous itself runs over Gloo and needs the same instruction separately.
NCCL_IB_DISABLE
1
This flavour communicates between nodes over Ethernet, so InfiniBand discovery is skipped rather than attempted and timed out.
SGLANG_HOST_IP
the private address of this node
Each rank advertises the address its peers can reach, which is the private one and never the floating IP.
The private network is reachable only inside your Hyperstack environment, which is why the firewall rule in Step 2 can open the whole 10.0.0.0/8 range to itself without exposing anything.
📘
Tensor parallel 16 is a consequence, not a preference. The group is 16 wide because 80 GB cards need 64 of them to hold 2.50 TB, and 64 GPUs admit only three legal splits. Card size is what moves this number, and the cost section prices that out.
How to Deploy Qwen3.8 Max on Hyperstack, Step by Step
Nine steps take a Qwen3.8 Max deployment from an empty account to an endpoint answering requests. Everything up to Step 5 is ordinary virtual machine work repeated eight times. The launch itself is Steps 6 and 7, and Step 7 is where the flags specific to this model appear.
1
Create eight virtual machines One environment, one flavour, one image, one SSH key.
2
Open the three ports that matter Two locked to your own address, one open inside the private range.
3
Point the model cache at the ephemeral disk The root disk holds 96 GB and the checkpoint is 2.50 TB.
4
Pull the serving image and the weights 213 shards on all eight nodes, in parallel.
5
Confirm every node before you commit to a launch Twenty seconds of checks protect a 64-rank rendezvous.
6
Set the launch environment Rank, private address, head address, interface.
7
Launch all eight ranks The same command everywhere, with the rank changing.
8
Watch the start-up Five memory checkpoints tell you it is going well.
9
Send the first request The endpoint speaks the OpenAI chat completions API.
Step 1: Create Eight Virtual Machines
From the Hyperstack dashboard, open the virtual machines page and start a new deployment. The getting started guide covers the account setup if this is a first deployment.
Start from the virtual machines page and select deploy a new virtual machine.
Four choices matter, and one of them matters more than the rest.
What to select, and why
The environment field is the one that quietly decides whether this cluster can form at all.
Field
Selection
Why it matters
Environment
One environment for all eight
Nodes share a private network only within an environment. Split them and the ranks cannot reach each other.
Flavour
8x NVIDIA H100 80GB PCIe
Eight nodes of eight cards give the 64 GPUs the checkpoint needs. Browse the full list in the flavour reference.
Image
Ubuntu Server 24.04 LTS R570 CUDA 12.8 with Docker
Driver, CUDA and container runtime arrive matched to the serving image, so there is no driver work at all.
SSH key
A key already imported to the account
Import it first from the key pairs page. A machine created without one cannot be reached.
Floating IP
Enabled
You need to reach each node once to set it up, and the head node again to serve traffic.
Name the machines predictably, for example q38-0 through q38-7. Rank 0 becomes the head node and the endpoint you eventually call.
The image picker. Choosing the CUDA and Docker variant removes every driver step from this deployment.
💡
Spot pricing changes the arithmetic on a cluster this size. An eight-node deployment costs eight times whatever one node costs. Spot virtual machines put NVIDIA H100 80GB PCIe at $2.00 per GPU hour on the published pricing, which is $16.00 per node and $128.00 per hour for this cluster, billed by the minute.
Step 2: Open the Three Ports That Matter
A multi-node deployment needs the nodes to talk to each other freely and needs you to talk to exactly one of them. Attach these firewall rules to every machine.
Firewall rules for the cluster
Two rules scoped to your own address, and one scoped to the private range.
Direction
Protocol and port
Source
Purpose
Ingress
TCP 22
your address /32
SSH for setup and launch
Ingress
TCP 30000
your address /32
The inference endpoint, on the head node
Ingress
TCP 1 to 65535
10.0.0.0/8
Rendezvous and collectives between ranks
The third rule looks broad and is not: 10.0.0.0/8 is private address space reachable only from inside your environment. The rendezvous port, the collective ports and the pipeline transfers all live in there.
Step 3: Point the Model Cache at the Ephemeral Disk
Once the machines are active, connect to each one and take stock. The root disk is 96 GB, which is not where 2.50 TB of weights is going. This flavour ships an ephemeral NVMe disk mounted at /ephemeral, and that is the destination.
Terminal, all eight nodes: 1 of 2, taking stockSHELL
# From your workstation, once the eight virtual machines are ACTIVEssh-i ~/.ssh/[YOUR KEY] ubuntu@[NODE PUBLIC IP]
# Confirm what you are standing on before anything elsenvidia-smi--query-gpu=index,name,memory.total --format=csv
ip-o -4 addr show |awk'{print $2, $4}'|grep' 10\.'df-h / /ephemeral
Expected output, node 0: the GPU and interface linesOUTPUT
Every card reports 81,559 MiB and the private interface is ens6, holding a 10.x address. The third command is the one that decides where the weights go: the root disk is 96 GB, so /ephemeral has to take the 2.50 TB checkpoint. Confirm it has the room before starting the download.
Terminal, all eight nodes: 2 of 2, preparing the cacheSHELL
# The root disk is 96 GB and the checkpoint is 2.50 TB, so the cache# has to live on the ephemeral NVMe disk. Do this on all eight nodes.sudomkdir-p /ephemeral/hf
sudochown-R ubuntu:ubuntu /ephemeral/hf
python3-mpip install -q--break-system-packages huggingface_hub hf_transfer
Step 4: Pull the Serving Image and the Weights
The container image and the checkpoint can be fetched at the same time, and on eight nodes both should be started in the background so a dropped SSH session does not take the download with it. The image comes from the SGLang project and carries the kernels this model needs.
Terminal, all eight nodes: the serving imageSHELL
# Pull the serving image in the background while the weights download.# Images stay on the root disk; only the checkpoint goes to /ephemeral.nohupdocker pull lmsysorg/sglang:qwen38 > /tmp/pull.log 2>&1&
The weights need a Hugging Face token with access to the Qwen3.8-2.4T-A95B-FP8 repository. Create one from your account settings and keep it out of your shell history.
Terminal, all eight nodes: 2.50 TB of weightsSHELL
# 2.50 TB across 213 safetensors shards. hf_transfer keeps the NVMe disk fed;# 32 workers saturated the link on our nodes without stalling.export HF_HOME=/ephemeral/hf
export HF_HUB_ENABLE_HF_TRANSFER=1export HF_TOKEN=[YOUR HUGGING FACE TOKEN]
nohuppython3-c"
from huggingface_hub import snapshot_download
snapshot_download('Qwen/Qwen3.8-2.4T-A95B-FP8', max_workers=32)
"> /tmp/dl.log 2>&1&
📘
Every node downloads the whole checkpoint. There is no sharding of the download by rank. Each node reads the shards it needs at load time and ignores the rest, so all eight machines pull 2.50 TB and the cluster moves 20 TB in total. This is the longest step in the deployment by a wide margin, and it is the one to start first.
Step 5: Confirm Every Node Before You Commit to a Launch
A 64-rank job starts by having all 64 ranks meet each other. Every rank waits for the others, so the cluster is only as ready as its least ready node. Checking takes about twenty seconds per machine, and it belongs immediately before the launch rather than earlier in the session.
Terminal, all eight nodes: the shard countSHELL
# Run this on every node and read the number, not the vibe. A shard that is# still a .incomplete temporary file fails the launch twenty minutes in,# after the rendezvous has already committed the other 63 ranks.
SHARDS=$(find /ephemeral/hf -name'*.safetensors'|wc-l)
PARTIAL=$(find /ephemeral/hf -name'*.incomplete'|wc-l)
echo"$(hostname): $SHARDS/213 shards, $PARTIAL incomplete"
The shard count is the check that pays for itself most often, because a download that stopped short looks completely normal until the engine reaches the missing file twenty minutes into loading. Three more checks round it out.
Terminal, all eight nodes: the rest of the gateSHELL
# One last look at every node, immediately before the launch rather than# minutes before it. Twenty seconds here protects a 64-rank rendezvous.
timeout 25nvidia-smi-L|grep-c'^GPU'# expect 8nvidia-smi--query-gpu=ecc.mode.current --format=csv,noheader |sort-uip-o -4 addr show |awk'$4 ~ /^10\./ {print $2; exit}'find /ephemeral/hf -name'*.safetensors'|wc-l# expect 213
What each check is protecting
Four cheap reads, each standing in front of a failure that would otherwise appear much later and cost much more.
Check
Expected
What it prevents
nvidia-smi -L returns within a timeout
8 GPUs
A node that answers slowly holds up the whole rendezvous.
ecc.mode.current
Enabled on all cards
Error correction is what turns a marginal memory cell into a logged event rather than a quietly wrong number inside 2.50 TB of weights.
A live 10.x interface
One name, for example ens6
NCCL_SOCKET_IFNAME has to name an interface that exists on that machine.
Shard count
213, with none incomplete
A missing shard fails the load long after the cluster has committed to it.
Treat any node that does not pass as a node to rebuild rather than a node to try anyway. Nothing about a 64-rank launch improves by starting it with seven good machines.
Step 6: Set the Launch Environment
Four values differ per node. Everything else in the launch is identical across the cluster, which is what makes this straightforward to script.
Terminal, each node: 1 of 3, what changes per machineSHELL
# Run on every node. RANK is 0 on the head node, then 1 through 7.
RANK=[THIS NODE RANK]
SELF=[THIS NODE PRIVATE IP]
HEAD=10.0.0.210 # the private address of rank 0
NIC=ens6 # the interface holding the 10.x address
The head address is the private address of rank 0 and is the same on every node. The rank is the one value that has to be unique across the cluster, so set it from the machine name rather than by hand if you are scripting this.
Step 7: Launch All Eight Ranks
The arguments split into three groups: the topology, the backends the hardware calls for, and the parsers that shape the responses. Building them into one variable keeps the launch readable and keeps every node running the same string.
Terminal, each node: 2 of 3, the server argumentsSHELL
# The topology and the two backend choices that follow from the hardware
Q38_ARGS="--trust-remote-code \
--model-path Qwen/Qwen3.8-2.4T-A95B-FP8 \
--tp-size16--pp-size4--nnodes8 \
--node-rank $RANK \
--dist-init-addr $HEAD:20000--dist-timeout1800 \
--linear-attn-prefill-backend flashinfer \
--linear-attn-decode-backend flashinfer \
--mamba-full-memory-ratio0.95 \
--mamba-ssm-dtype bfloat16 \
--mamba-radix-cache-strategy extra_buffer \
--max-prefill-tokens8192--page-size64 \
--reasoning-parser qwen3 --tool-call-parser qwen3_coder \
--host 0.0.0.0 --port30000"
The flags that are specific to Qwen3.8 Max
Everything else in that command is topology. These are the ones that exist because of what this model is.
The Gated DeltaNet layers default to a Triton path on this GPU generation. NVIDIA H100 and NVIDIA H200 share compute capability 9.0, so a kernel choice verified on one carries to the other, and pinning both halves to FlashInfer is what this deployment ran.
--mamba-full-memory-ratio 0.95
Sets how much of the free memory becomes recurrent state pool. This is the value behind every figure here, and a starting point rather than a figure tuned for this hardware.
--mamba-ssm-dtype bfloat16
Halves the size of each state slot against float32, which directly raises how many requests can run at once.
--mamba-radix-cache-strategy extra_buffer
Required to keep --page-size 64. Without it the recurrent cache requires a page size of 1, at a cost in paging efficiency.
--reasoning-parser qwen3
Returns the chain of thought in its own response field instead of inline inside the answer.
--tool-call-parser qwen3_coder
Turns the native tool syntax of this model into OpenAI-shaped tool_calls.
Flags are verified against a specific build. Setting the recurrent cache strategy explicitly rather than leaning on a default is what keeps a launch working across an image update.
The container itself needs host networking so the ranks can reach each other on the private addresses, the checkpoint mounted from the ephemeral disk, and enough shared memory for the loader. Run this on all eight nodes.
Launch the head node first, then the rest together. Rank 0 owns the rendezvous address, so starting it a moment ahead of the others means every other rank finds it on the first attempt. The --dist-timeout 1800 setting gives the group thirty minutes to form, which is generous for 64 ranks and worth keeping while you are getting familiar with the Docker workflow on these machines.
Step 8: Watch the Start-up
The engine reports its memory at five points, and those five numbers are the clearest signal that a launch is going well. Follow the head node.
Terminal, head node: following the interesting linesSHELL
# On the head node. The interesting lines are the memory checkpoints.docker logs -f q38 2>&1|grep-E "Init torch|Load weight|Cache is allocated|\
Memory pool|CUDA graph (begin|end)|max_total_num_tokens|Uvicorn"
The ranks meet first. Twenty-six seconds for 64 processes to join one group is normal, and the two lines about collectives are the engine adapting to a group that spans machines rather than anything going wrong.
Free memory falling from 78.05 GB to 41.73 GB is the moment the deployment becomes real. That 36.32 GB per GPU is the FP8 checkpoint spread across 64 cards. Our first load took 5 minutes 38 seconds reading cold from disk. On a relaunch with the page cache still warm the first rank finished in 45 seconds and the last in 55, and it is the slowest rank that decides when the cluster moves on.
Start-up: 2 of 3, the pools are builtOUTPUT
[18:16:45 PP0 TP0] max_running_requests is capped to 541 by the mamba state
cache (max_mamba_cache_size=2164, 4 state slots per request).
[18:16:46 PP0 TP0] Mamba Cache is allocated. max_mamba_cache_size: 2164,
conv_state size: 0.28GB, ssm_state size: 9.51GB
[18:16:46 PP0 TP0] KV Cache is allocated. dtype: torch.bfloat16,
#tokens: 1803072, K size: 4.30 GB, V size: 4.30 GB
[18:16:46 PP0 TP0] Memory pool end. avail mem=22.78 GB
This is where the hybrid attention stack shows up in the accounting. The recurrent state pool takes 9.79 GB and the key-value cache takes 8.60 GB, and it is the recurrent pool that sets the ceiling on concurrent requests. The engine states the limit and the arithmetic behind it in one line: 2,164 state slots, four slots per request, so 541 requests can run at once.
Start-up: 3 of 3, the graphs and the capacityOUTPUT
[18:19:48 PP0 TP0] Capture target prefill CUDA graph end. elapsed=182.25 s,
mem usage=3.87 GB, avail mem=18.76 GB.
[18:20:27 PP0 TP0] Capture target decode CUDA graph end. elapsed=38.97 s,
mem usage=1.42 GB, avail mem=17.34 GB.
[18:20:27 PP0 TP0] max_total_num_tokens=1803072, chunked_prefill_size=8192,
max_prefill_tokens=8192, max_running_requests=541,
context_len=262144, available_gpu_mem=17.34 GB
[18:20:28] INFO: Uvicorn running on http://0.0.0.0:30000
Where the 78.05 GB on each GPU goes
Measured from the free-memory checkpoints the engine prints, so the segments are differences between real readings rather than an estimate.
Capturing CUDA graphs is the longest phase of a warm start. The prefill capture walks 58 different token counts and the decode capture walks 52 batch sizes, and together they account for more than half the time between launching the container and answering a request.
The cold start, phase by phase
Total time from container start to a live endpoint was five minutes and 42 seconds on a warm page cache.
Weight load shown at the warm figure of 55.0 seconds. Reading the shards cold from the ephemeral disk the first time took 5 minutes 38 seconds, which puts a fully cold start at roughly ten minutes.
The same start-up as a timeline
Eight moments worth recognising in the log, with the three that confirm progress marked.
00:00Container starts
The image is already on disk, so this is process start rather than a pull.
00:28Rendezvous begins
26.6 seconds for all 64 ranks to join the process group.
00:58Weight load begins
213 shards read from the ephemeral disk on all eight nodes at once.
01:53Weights resident
36.32 GB per GPU. Free memory falls from 78.05 GB to 41.73 GB.
02:00Memory pools built
Recurrent state and KV cache together take 18.95 GB, leaving 22.78 GB.
05:02Prefill graphs captured
182.6 seconds across 58 token counts. This is the long pole.
05:41Decode graphs captured
39.0 seconds across 52 batch sizes.
05:42Endpoint answers
max_total_num_tokens 1,803,072, context 262,144.
Timestamps from the head node log of this deployment.
The endpoint speaks the OpenAI chat completions API on port 30000 of the head node. No other rank needs to be reachable from outside the environment.
Terminal, your workstation: the first callSHELL
# From your workstation, against the head nodecurl-s http://[HEAD NODE IP]:30000/v1/models |head-c200curl-s http://[HEAD NODE IP]:30000/v1/chat/completions \
-H'Content-Type: application/json' \
-d'{
"model": "Qwen/Qwen3.8-2.4T-A95B-FP8",
"messages": [{"role": "user", "content": "Reply with exactly: OK"}],
"max_tokens": 512
}'
While that runs, the cluster looks like this. Roughly 63,400 MiB in use of the 81,559 MiB on each card, at low temperature, because a mixture-of-experts model at low concurrency waits on memory traffic rather than saturating the compute.
nvidia-smi during serving, one node, abridgedOUTPUT
One endpoint, four pipeline stages, and a tensor-parallel group of 16 inside each stage.
Only rank 0 needs the floating IP. The remaining seven nodes work entirely on the private network.
Qwen3.8 Max Inference: Reading What Comes Back
Qwen3.8 Max reasons before it answers, on every request. That single fact changes how a client has to be written, and it is the part of a Qwen3.8 Max implementation most likely to catch a team that has ported code from a model without chain of thought.
The Chain of Thought Arrives in Its Own Field
Because the server was launched with --reasoning-parser qwen3, the reasoning does not appear inline in the answer wrapped in tags. It comes back in message.reasoning_content, and the answer in message.content is clean. A client can log the reasoning, show it, or discard it, without parsing anything.
qwen38_client.py: 1 of 3, one request, standard library onlyPYTHON
import json
import urllib.request
BASE = "http://[HEAD NODE IP]:30000/v1"
MODEL = "Qwen/Qwen3.8-2.4T-A95B-FP8"def ask(prompt, max_tokens=4096):
body = {
"model": MODEL,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
"top_p": 0.8,
"max_tokens": max_tokens,
}
request = urllib.request.Request(
BASE + "/chat/completions",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(request, timeout=1800) as response:
return json.load(response)
qwen38_client.py: 2 of 3, reading the responsePYTHON
def show(prompt, max_tokens=4096):
payload = ask(prompt, max_tokens)
choice = payload["choices"][0]
message = choice["message"]
usage = payload["usage"]
# The qwen3 reasoning parser puts the chain of thought in its own field,# so the answer arrives clean and the reasoning can be logged or dropped.
thinking = message.get("reasoning_content") or""
answer = message.get("content") or""print("finish_reason :", choice["finish_reason"])
print("reasoning :", usage["reasoning_tokens"], "tokens")
print("completion :", usage["completion_tokens"], "tokens")
print("answer :", len(answer), "characters")
return answer
The usage block carries a reasoning_tokens field alongside completion_tokens, which is the number to watch. Reasoning is not billed separately in a self-hosted deployment, but it does consume the same budget as the answer.
Reasoning and the Answer Share One Token Budget
This is the behaviour to design around. max_tokens covers the reasoning and the answer together, not the answer alone. We sent the same code prompt at two budgets.
One budget, two outcomes
The same prompt, the same model, the same sampling settings. Only max_tokens changed.
max_tokens 2,048Answer truncated in the middle of a function
1,871 reasoning tokens
177
max_tokens 6,144No answer tokens at all
6,144 reasoning tokens
Both responses came back with finish_reason set to length. The second one carried an empty content field and 6,144 reasoning tokens.
Captured from the two code prompts in this deployment. At 2,048 tokens the answer stopped in the middle of a function body; at 6,144 the content field came back empty.
At 6,144 the model spent the entire allowance thinking and emitted no answer tokens at all. A budget sized for a model without chain of thought does more than clip the tail here. It can return an empty response, or code that looks complete and is not.
qwen38_client.py: 3 of 3, the check that belongs in every clientPYTHON
# A budget sized for a model without chain of thought can return nothing at# all here, because reasoning and the answer share one allowance. Treat a# length stop as a retry signal rather than as a finished response.
answer = show("Write a Python function chunked(iterable, n) that yields ""lists of length n, lazily, raising ValueError if n < 1.",
max_tokens=4096)
ifnot answer.strip():
print("empty answer: raise max_tokens or lower reasoning_effort")
⚠️
Check finish_reason on every response. A value of length means the budget ran out, and on this model that can mean an answer that stops mid-token or no answer at all. Treat it as a retry signal with a larger budget or a lower reasoning effort, never as a completed response.
Reasoning Effort Is the Control Worth Wiring Up
The Qwen3.8-2.4T-A95B model card documents three reasoning levels, low, medium and xhigh, with xhigh as the default. Every measurement below was taken at the default, which is the most expensive setting and the right one to measure against. It is also the first lever to reach for when a workload does not need deep deliberation on every turn.
Request body, asking for a shorter deliberationJSON
# reasoning_effort is the documented control. The default is xhigh, which is# what every measurement above was taken at.
{
"model": "Qwen/Qwen3.8-2.4T-A95B-FP8",
"messages": [{"role": "user", "content": "..."}],
"reasoning_effort": "low",
"max_tokens": 4096
}
How much of each response was reasoning
Five prompts of rising difficulty. Each bar is one response at 100 per cent, split into thinking and answering, with its total completion tokens above it.
Taken from the usage block of each captured response. The rightmost bar is the 6,144 token budget that returned no answer at all.
The shape of that chart is the useful part. On a two-sentence factual question the model spent 105 tokens thinking and 65 answering. On a code task it spent 1,871 thinking and 177 answering. Reasoning grows with difficulty far faster than the answer does, so a fixed budget that suits simple traffic will fail on hard traffic rather than degrade on it.
Tool Calling Works End to End
The qwen3_coder parser turns the native tool syntax of this model into OpenAI-shaped tool_calls. We tested it with a loop that executes the tools and feeds each result back, because a broken parser still produces plausible-looking first-turn output.
Tool definition, one of fivePYTHON
TOOLS = [{
"type": "function",
"function": {
"name": "plan_cluster",
"description": "Smallest cluster that leaves more than headroom_gb ""free per GPU and admits a legal (tp, pp) split.",
"parameters": {
"type": "object",
"properties": {
"weights_tb": {"type": "number"},
"vram_gb": {"type": "number"},
"gpus_per_node": {"type": "integer"},
"layers": {"type": "integer"},
"headroom_gb": {"type": "number"},
},
"required": ["weights_tb", "vram_gb", "gpus_per_node", "layers"],
},
},
}]
The loop, with results fed back as role toolPYTHON
messages = [
{"role": "system", "content": "You are a deployment engineer. Call a tool ""for every factual number."},
{"role": "user", "content": TASK},
]
for turn inrange(8):
payload = chat(messages, TOOLS)
message = payload["choices"][0]["message"]
calls = message.get("tool_calls") or []
# Echo the assistant turn back so the model sees its own calls
messages.append({k: v for k, v in message.items()
if k != "reasoning_content"})
ifnot calls:
print(message["content"])
breakfor call in calls:
name = call["function"]["name"]
args = json.loads(call["function"]["arguments"] or"{}")
result = IMPLEMENTATIONS[name](**args)
messages.append({"role": "tool", "tool_call_id": call["id"],
"name": name, "content": json.dumps(result)})
The task was to size a cluster for this exact model using only tool results. It made four dependent calls in the right order, taking each argument from the previous result, across eight turns and 54.6 seconds.
The captured tool trace, four dependent callsOUTPUT
The final answer then reproduced the non-obvious part without being prompted for it: six and seven node clusters fit the weights in memory and admit no legal placement, so they are infeasible. That is the same conclusion the architecture section reached, arrived at independently from the tool output.
It Gets the Arithmetic Right
Two of the prompts had ground truth we could check rather than eyeball. Asked for the annual cost of a 6.5 kW rack at $0.11 per kWh with cooling billed at 40 per cent of the power bill, it returned $6,263.40 for power, $2,505.36 for cooling and $8,768.76 in total, which matches an independent calculation exactly. Asked how 92 layers can be split evenly across pipeline stages, it returned the full set.
Response, the pipeline question, abridgedOUTPUT
The possible numbers of pipeline stages are 2, 4, 23, 46 and 92.
Let s be the number of stages. For exactly equal layers per stage, 92 / s
must be an integer, so s must divide 92. The prime factorisation is
92 = 2^2 x 23, so its positive divisors are 1, 2, 4, 23, 46 and 92.
Excluding a single stage leaves 2, 4, 23, 46, 92.
Throughput, and What Sets It
We swept concurrency from one request to 32 against the live endpoint, with a 256 token cap on each, and measured what came back.
Terminal, your workstation: the concurrency sweepSHELL
Aggregate output rises 7.5 times from one request to 32. Per-stream speed falls, which is the expected trade.
Every request in the sweep succeeded: 53 in total across the four levels, with no failures at any concurrency.
CONCURRENCY 1
10.9
aggregate tokens per second
10.9 per stream 22.4 second median latency
CONCURRENCY 4
30.8
aggregate tokens per second
7.8 per stream 32.3 second median latency
CONCURRENCY 16
45.5
aggregate tokens per second
2.9 per stream 86.8 second median latency
CONCURRENCY 32
81.6
aggregate tokens per second
2.7 per stream 95.3 second median latency
The Recurrent State Pool Is the Ceiling, Not the Context Length
On a conventional transformer, concurrency runs out when the key-value cache fills, so long prompts and many users compete for the same resource. Here the engine reported a limit of 541 concurrent requests set by the recurrent state pool, from 2,164 state slots at four slots per request. That limit does not move with prompt length, because a Gated DeltaNet layer carries the same fixed state whether the sequence is a thousand tokens or a hundred thousand.
Two flags move it. Raising --mamba-full-memory-ratio gives the pool more of the free memory, and --mamba-ssm-dtype bfloat16 halves the size of every slot against float32. Both are already set in the launch above.
Two Limits on These Figures
These numbers are a floor rather than a ceiling, for two reasons.
What is left on the table
Both are headroom that a production deployment can claim.
What
Effect
What to do about it
The mixture-of-experts kernels are running an untuned configuration
The engine ships tuned configurations per model, GPU and tensor degree, and there is none published for 512 experts at tensor parallel 16 on NVIDIA H100. It says so during start-up.
Tune the kernel configuration for this shape, or move to a placement that has a published configuration.
No speculative decoding
The checkpoint contains a multi-token prediction head, and a pipeline degree above one disables it. Single-token decoding is the slow path for a sparse model.
Fewer, larger GPUs allow pipeline degree 1, which brings the head back into play.
The start-up log names the missing kernel configuration explicitly, which is a useful thing to grep for on any new placement.
Start-up, the kernel configuration noticeOUTPUT
Using default MoE kernel config. Performance might be sub-optimal!
Config file not found at .../configs/triton_3_7_1/
E=512,N=128,device_name=NVIDIA_H100_PCIe.json
📘
The route to more speed is fewer, larger cards. Every constraint here traces back to fitting 2.50 TB onto 80 GB cards. NVIDIA H200 SXM at 141 GB per card halves the GPU count, which narrows the tensor group and keeps far more traffic inside a machine. NVIDIA Blackwell with a 4-bit checkpoint brings the whole model into a single node, which removes the pipeline dimension and the constraint that comes with it.
Qwen3.8 Max Deployment Cost
A Qwen3.8 Max deployment cost is easy to reason about once the node count is settled, because the cluster bills as eight identical machines by the minute. The rate is the same whether the GPUs are loading weights, capturing graphs or serving traffic, so the only open question is how long each phase lasts.
What it costs to hold 2.50 TB of weights for an hour
This deployment is the first row. The others carry the same checkpoint on different hardware.
Per GPU rates from the Hyperstack GPU pricing page, multiplied by the GPU count each configuration needs. The NVIDIA H200 SXM row is 32 GPUs rather than 64, because 141 GB cards hold 2.50 TB in half as many, and that puts an hour of it within 32 cents of the Spot cluster used here.
What Each Phase Cost in This Deployment
These are the phases we timed, priced at the Spot cluster rate of $128.00 per hour, which is $2.13 a minute. Everything here is measured rather than modelled.
Measured phases and their cost
A deployment spends most of its money before it answers a single request, and then the bill settles into a flat hourly rate.
Phase
Duration
Cost at $2.13 per minute
How often you pay it
Engine start, warm page cache
5 min 42 s
$12.14
Every launch after the first
Engine start, cold from disk
about 10 min
about $21.30
The first launch of a session
Chat and reasoning prompts, four of them
5 min 50 s
$12.43
Workload dependent
Tool-calling scenarios, two of them
3 min 9 s
$6.70
Workload dependent
Concurrency sweep, 1 to 32
3 min 58 s
$8.46
Workload dependent
Steady-state serving
per hour
$128.00
Continuously, while running
Durations from the captured latencies of each run. The 2.50 TB download runs before any of this and is the longest single phase in the deployment, so start it as soon as the machines are active and do the firewall work while it runs.
The Three Levers That Move the Bill
Only one of these is a tuning exercise. The other two are decisions taken before the cluster exists.
What to change, in order of effect
Node count sets the floor, the pricing model scales it, and start-up time is what you pay repeatedly while iterating.
Lever
Effect
Card size
The largest lever, and the table above prices it: half the GPUs for the same money, with 62.9 GB free per GPU rather than 40.9 GB. A 4-bit NVIDIA Blackwell checkpoint at 1.32 TiB goes further again and fits one node.
Pricing model
Spot is the cheapest route onto NVIDIA H100 80GB PCIe at $2.00 per GPU hour. On the SXM flavours the lever is a reservation instead, which starts at $2.72 per GPU hour against $3.20 on demand.
Start-up time
Capturing CUDA graphs is 221.6 seconds of every launch. That cost is fixed per launch rather than per request, so it matters while you are iterating and disappears once the endpoint is long-lived.
Billing is per minute of runtime rather than by the hour, so a short experiment is charged as a short experiment.
📘
Why self-host a model that is also available as an API. The open checkpoint gives you the full 262,144 token context with no per-token metering, the sampling and reasoning controls in your own hands, weights that stay inside your own environment, and an activated parameter count you can read. Those are the reasons a Qwen3.8 Max deployment earns its cluster, rather than a per-token comparison.
Why Deploy Qwen3.8 Max on Hyperstack?
Hyperstackis a cloud platform built for AI andmachine learningworkloads. Here is what a 64-GPU deployment needs from a provider, and how that maps onto the platform:
64 NVIDIA H100 GPUs on One Private Network Eight nodes of NVIDIA H100 80GB PCIe carry 39.1 GB per GPU. One environment gives them a private network, and all 64 ranks met in 26.6 seconds.
NVMe Storage Sized for a 2.50 TB Checkpoint A 96 GB root disk cannot hold 213 shards. The ephemeral NVMe disk at /ephemeral took the whole 2.50 TB on every node.
Matched Driver, CUDA and Container Runtime The Ubuntu 24.04 R570 CUDA 12.8 with Docker image already matches the serving image, so every node goes from ssh to docker run with no driver work.
Firewall Rules Scoped to the Cluster Firewall rules attach per machine, so SSH and the endpoint stay locked to your own address while 10.0.0.0/8 opens only inside the environment.
Eight nodes on Spot at $128.00 per hour. Our deployment loaded 2.50 TB across 64 GPUs, reached a live endpoint in under six minutes of engine start-up and served the full concurrency sweep without a single failed request.
64x NVIDIA H100 80GBTensor 16 x pipeline 4262,144 token context$128.00 per hour
Launch an NVIDIA GPU cluster on Hyperstack today.
FAQs
What hardware do you need to deploy Qwen3.8 Max?
The FP8 checkpoint is 2.50 TB, which is 39.1 GB per GPU across 64 cards. We ran it on eight Hyperstack nodes of 8x NVIDIA H100 80GB PCIe, leaving 17.34 GB free per GPU once the caches and CUDA graphs were resident.
How much does a Qwen3.8 Max deployment cost?
Eight nodes of 8x NVIDIA H100 80GB PCIe on Spot are $2.00 per GPU hour, so $128.00 an hour for the cluster, billed by the minute. A warm engine start costs $12.14 and the full concurrency sweep cost $8.46.
Can you turn thinking off on Qwen3.8 Max?
No. Every completion carries chain of thought, and reasoning shares the same max_tokens budget as the answer. The documented control is reasoning_effort, which takes low, medium or xhigh, and defaults to xhigh.
Why does Qwen3.8 Max need eight nodes and not six?
Six nodes hold the weights with 27.9 GB free per GPU, then fail on arithmetic. Tensor parallel 24 and 12 divide neither the 64 query heads nor the layer count, so no legal placement exists. Seven nodes fail the same test.
How long does Qwen3.8 Max take to start serving?
Five minutes 42 seconds from container start to a live endpoint with a warm page cache, or roughly ten minutes reading the 213 shards cold. Capturing CUDA graphs is 221.6 seconds of that and happens on every launch.
Deploy MiniMax H3 on GPU Cloud for Video and Audio in One Pass
MiniMax H3 is a 33 billion parameter omni-modal generative ...
MiniMax H3 is a 33 billion parameter omni-modal generative system, open sourced on 3 August 2026, and it does something the text-to-video models before it did not: it writes the soundtrack at the same time as the picture. Video latents and audio latents are predicted by the same transformer, in the same forward pass, from one packed multimodal sequence. What lands on disk is a single MP4 carrying H.264 video at 24 frames per second and AAC stereo at 32 kHz, already in sync, with no separate audio model and no post-production step.
This guide serves it on one Hyperstack node: four NVIDIA H100 80GB PCIe GPUs on Spot at $8.00 per hour, through SGLang Diffusion. Every number below comes from a single session on 11 August 2026 that pulled 196 GB of checkpoints, reached a live endpoint, and produced eleven finished clips across all three task modes. Total generation time on the four GPUs was 51.5 minutes for 65.3 seconds of video, and every clip was verified with ffprobe to be carrying two channels of 32 kHz audio.
Serving it yourself means three decisions: how to place the model across four GPUs, how to configure the collectives for the fabric those GPUs sit on, and how to write a prompt without the hosted preprocessor that normally rewrites one. If you have read our technical deep dive into Kimi K3, this is the same shape of exercise on a much smaller cluster and a generative rather than a language workload. If you want a lighter video model on a single GPU first, our Wan 2.1 video generation tutorial covers that ground.
What MiniMax H3 Is, and Which Part of It You Can Run Yourself
H3 is not one model. The model card describes three modules, and only the middle one has open weights. Getting that distinction right before you book hardware saves a great deal of confusion later, because most of the published sample output was produced by all three working together.
The three modules of MiniMax H3
H3-Context-IR turns free-form input into structure, H3-Base generates, H3-Regenerate-2K redraws at higher resolution. Only the middle box has published weights.
Redrawn from the system overview on the MiniMaxAI/MiniMax-H3 model card.
That leaves a self-hosted deployment with two consequences and one advantage. The consequences: output is capped at a 768 pixel short edge, and nothing rewrites a rough prompt into something the model understands well. The advantage: the weights are the whole system you are running, so results are reproducible from a seed and a prompt, with no hidden service in the middle changing what you asked for.
Inside H3-Base
A 33 billion parameter dense single-stream transformer, fed by a Qwen3-VL text and vision encoder and two separate variational autoencoders, predicting picture and sound together.
33B dense Omni-TransformerQwen3-VL-32B encoderBF16CFG-distilled24 fps32 kHz stereo4 to 15 secondsMM-RoPE over t, h, w
TRANSFORMER
33B parameters
single stream, roughly 13B of it in AdaLN branches that are cacheable at inference
TEXT AND VISION
Qwen3-VL-32B
full pretrained weights, hidden states taken from the 50th layer
one encoder shared by both channels, run independently, recombined into stereo
ATTENTION
Full, in this release
sparse attention was trained but is not part of the initial open source drop
CHECKPOINTS
Two partitions
FL2VA serves t2va and fl2va, Ref2VA serves ref2va and needs its own server
Architecture figures from the MiniMaxAI/MiniMax-H3 model card and the MiniMax H3 research write-up. Read them alongside the licence: the weights carry the MiniMax H3 Community License Agreement rather than a stock open source licence, and MiniMax publishes a separate application form covering the USA, EU, UK and South Korea.
The two output streams stay together because of how the sequence is built. Text goes through the H3-Encoder. Images and video go through the H3-Encoder and the visual VAE. Audio goes only through the audio VAE. All of it is then packed into one sequence with three-dimensional rotary position embeddings over time, height and width, and the transformer predicts video latents and audio latents jointly. Synchronisation is not a post-process, it is a property of the sequence.
One forward pass, two decoders
Nothing in the attention or feed-forward layers is modality specific. Only the input and output layers and the AdaLN branches know which modality they are looking at.
Drawn from the architecture section of the MiniMaxAI/MiniMax-H3 model card. The frame count shown is from our own 6 second 16:9 output.
What is open, and what is not
Component
In the open release
What it means for a deployment
H3-Base FL2VA
Yes, BF16 weights
Text-to-video-audio and first or last frame conditioning, from one server
H3-Base Ref2VA
Yes, BF16 weights
Reference conditioning on images, video and audio, from a second server
H3-Context-IR
No, hosted API
You write the structured prompt yourself, to the published specification
H3-Regenerate-2K
No, hosted API
Self-hosted output stays at a 768 pixel short edge
Sparse attention
Not yet
Inference runs full attention, so cost grows quickly with sequence length
📘
Read the licence before you build on this. H3 ships under the MiniMax H3 Community License Agreement, not a stock open source licence, and MiniMax publishes a separate application form covering the USA, the EU, the UK and South Korea. The open source announcement and the model card are the two documents to read first.
The Arithmetic That Decides the Hardware
Two numbers set the shape of the deployment. The first is what the checkpoints weigh. The repository hosts the original checkpoint and the diffusers format side by side, so scoping the download matters: pulling only model_index.json, FL2VA/* and Ref2VA/* landed 196 GB across 163 files on our node.
The second is what has to be resident while a generation runs. The official vLLM recipe puts the BF16 footprint of one task partition at roughly 135 GiB before any activations: two DiTs at 66.3 GB each, the Qwen3-VL-32B encoder at 51.5 GB, the video VAE at about 10 GB and the audio VAE at about 0.6 GB. No single 80 GB card holds that, which is why four GPUs is the floor rather than a preference.
How those four GPUs are arranged is a choice rather than a default. The SGLang MiniMax-H3 cookbook publishes three placements for 4x NVIDIA H100 80GB, all of which keep the whole pipeline resident, and they trade latency against memory.
Three ways to place H3 on four NVIDIA H100 80GB GPUs
All three keep the pipeline resident. They differ by less than a second of pipeline latency and by 16 GB of peak memory per GPU, which is the entire trade-off.
Latency and peak memory from the SGLang MiniMax-H3 cookbook, hardware profile h100. The highlighted bar is the placement used throughout.
The cookbook labels tensor parallel 2 with Ulysses degree 2 the fastest measured placement on this hardware, and it leads by 0.61 seconds. It also states plainly that pure Ulysses degree 4 cannot keep the pipeline resident on 80 GB NVIDIA H100 cards. We ran tensor parallel 4 with Ulysses degree 1, the lowest-memory row, because it leaves the most headroom for a long reference clip and asks the least of the interconnect on a PCIe fabric.
💡
Sequence parallelism and tensor parallelism are not interchangeable here. Ulysses degree splits the sequence across GPUs and exchanges attention heads between them. Tensor parallel splits the weights. The product of the two has to equal the GPU count, so on four GPUs your only choices are 4x1, 2x2 and 1x4, and each one moves a different amount of data at a different point in the pipeline.
Matching the Configuration to Your Node
Two settings depend on how the four GPUs in your node are wired together. Each one is a single line in the launch command, and each one follows from a check that takes a second.
1. Read the fabric, then set NCCL to match it
Ask the driver how the four cards are connected, and ask CUDA what it will pass directly between them. The two answers agree, and together they tell you which transport NCCL should use.
Terminal, on the nodeSHELL
# What the driver reports about how the four cards are wired togethernvidia-smi topo -m# And what CUDA will actually permit between thempython3-c"
import torch
for i in range(4):
for j in range(4):
if i != j:
print(i, j, torch.cuda.can_device_access_peer(i, j))
"
Output of nvidia-smi topo -m and can_device_access_peerOUTPUT
PHB means every pair of GPUs communicates through a PCIe host bridge, which is the fabric on this flavour, and torch.cuda.can_device_access_peer reports False for all twelve ordered pairs. Collectives therefore travel through host memory, and NCCL is told so explicitly with two NCCL environment variables: NCCL_P2P_DISABLE=1 selects that path, and NCCL_CUMEM_ENABLE=0 keeps the allocator on the matching one. Neither costs anything measurable here, because the sequence is split four ways rather than thirty-two and the collectives are small next to the denoising loop they sit inside.
The same four GPUs, two different fabrics
A PCIe flavour carries every collective through the host bridge. An NVLink node carries them card to card. The model is identical either way, and only NCCL needs to know which it is on.
Topology read directly from nvidia-smi topo -m on the node, and peer access probed with torch.cuda.can_device_access_peer across all twelve ordered pairs.
📘
This is a property of the flavour, not of the GPU. Hyperstack also offers NVIDIA H100 PCIe-NVLink and NVIDIA H100 SXM flavours, which expose peer-to-peer directly. Run nvidia-smi topo -m on whatever you deploy: it takes a second, and it tells you which of the two columns above you are looking at.
2. Choose the parallel placement that suits the fabric
All three documented placements fit on four NVIDIA H100 cards. Tensor parallel 2 with Ulysses degree 2 is the fastest of them by 0.61 seconds, and it is the one to reach for on a node with peer-to-peer. On a PCIe fabric, tensor parallel 4 with Ulysses degree 1 is the better fit: it is the lowest-memory row at 49.80 GB per GPU, and with Ulysses degree 1 there are no sequence-parallel groups to form, so nothing crosses the host bridge that does not have to.
Trading 0.61 seconds of pipeline latency for 16 GB of headroom per card is a comfortable trade when a single Ref2VA job peaks at 54.9 GB as it is, and it leaves room for a longer reference clip than the fastest row would.
Where each placement sits against the 80 GB ceiling
Every configuration below fits. The question is how much room is left for a long reference clip, and how many groups the engine has to form to get there.
Cookbook figures for the three documented placements, and our own peak memory as reported by the server on the two Ref2VA jobs.
How to Deploy MiniMax H3 on Hyperstack
Now, let us walk through the step-by-step process of standing up the node and serving MiniMax H3 across all four GPUs.
📘
The whole exercise runs on one virtual machine. There is no cluster to form and no private network to configure, which makes it a considerably simpler deployment than a multi-node language model. For the parallelism theory behind the placement above, see How to Run Distributed Inference with vLLM.
Four things decide the flavour, and n3-H100x4 supplies all four.
What the deployment needs
What the flavour gives
Why it matters here
Four GPUs of at least 80 GB
4x NVIDIA H100 80GB PCIe
One task partition is about 135 GiB in BF16, so it has to be split four ways
Room for 196 GB of weights
3.2 TB ephemeral NVMe at /ephemeral
The 96 GB root disk cannot hold the checkpoints and the container image
A matched driver and container runtime
Ubuntu 24.04, R570, CUDA 12.8, Docker
The deployment goes from ssh straight to docker run
Host memory for the decode path
720 GB RAM, 124 vCPUs
Reference video is decoded on the host before it ever reaches a GPU
If you are new, create an account and set up your billing information. Our documentation can guide you through the initial setup.
Step 2: Deploying a 4x NVIDIA H100 Virtual Machine
From the Hyperstack dashboard, launch a single GPU virtual machine. The GPU count is fixed by the memory arithmetic above: one task partition is about 135 GiB in BF16, and no smaller shape holds it.
Initiate Deployment: Click the "Deploy New Virtual Machine" button on the dashboard.
The button sits above the virtual machine list, under Cloud then Virtual Machines.
Select Hardware Configuration: Choose a 4x NVIDIA H100 80GB PCIe flavour, listed in the flavour reference as n3-H100x4. Our run used the Spot variant, n3-H100x4-spot, at $2.00 per GPU per hour, which is $8.00 per hour for the node. The flavour also carries 124 vCPUs, 720 GB of RAM and a 3.2 TB ephemeral disk, all three of which this deployment uses.
Choose the NVIDIA H100-80G-PCIe card, then set the count to 4x in its dropdown.
Choose the Operating System: Select the "Ubuntu Server 24.04 LTS R570 CUDA 12.8 with Docker" image. The driver line has to match what the SGLang container expects, and this image also ships Docker and the NVIDIA container runtime ready to use, so there is no driver work at all.
Environment: Choose an environment carrying NVIDIA H100 stock. Our run used CANADA-1.
Select a Keypair: Choose an existing SSH keypair, or import one now. You will need it in step 4.
Network Configuration: Assign a Public IP so you can reach the machine over SSH.
Enable Ephemeral Storage: Make sure the ephemeral disk is attached. The checkpoints are 196 GB and the root disk is 96 GB, so this is not optional here.
Review and Deploy: Check the settings and click "Deploy". The machine reaches ACTIVE in a couple of minutes.
Open the Ubuntu dropdown and take the R570 CUDA 12.8 with Docker build.
💡
Spot capacity suits this workload well, with one condition.Spot VMs run on surplus capacity at a lower rate and can be reclaimed when that capacity is needed, so treat the node as disposable and copy each clip off as it finishes. For a generation run like this one it turns a $10.00 per hour node into an $8.00 per hour node.
Step 3: Configure the Firewall
This deployment needs one inbound rule, and one port deliberately left shut. Add the first under firewall rules on the virtual machine:
Port
Source
Why
22
your public IP /32
SSH access to the node
30010
closed
The inference API. Leave it shut and drive it from the node itself
Port 30010 stays closed deliberately. Every generation here runs from a script on the node, because reference images and clips are passed as file:// URIs that only resolve inside the server container. Driving the endpoint remotely would put those references out of reach and expose an unauthenticated API at the same time. If you do need it from your own machine, forward it over the SSH session you already have rather than opening a port:
Terminal, on your own machineSHELL
# Optional. Forwards the endpoint to localhost:30010 on your own machine without# opening anything on the node. Note that file:// conditioning still resolves only# inside the container, so image and video references need the on-node client.ssh-i /path/to/your-key -L30010:127.0.0.1:30010 ubuntu@[PUBLIC IP]
⚠️
The inference endpoint has no authentication. Anything that reaches port 30010 can spend your GPU hours four minutes at a time. Keep it bound to the node, and never open it to 0.0.0.0/0.
Step 4: Accessing Your Node
Once the virtual machine is running, copy its Public IP from the dashboard and connect.
Terminal, on your own machineSHELL
# The public IP is on the virtual machine's detail page in the dashboardssh-i /path/to/your-key ubuntu@[PUBLIC IP]
Confirm the two things this deployment depends on: four NVIDIA H100 cards, and a large disk mounted at /ephemeral.
The disk line is what shapes the rest of the guide. The machine has two disks and only one of them matters here: the root filesystem is roughly 96 GB, while the NVMe disk is mounted separately at /ephemeral and offers around 3.2 TB on this flavour. It is documented under ephemeral storage. The two checkpoint partitions are 196 GB together and the container image adds more on top, so the model cache has to live on the big disk.
Step 5: Prepare the Disks and Pull the Checkpoints
This script does four things: creates the working directories on /ephemeral, installs ffmpeg and jq, and then starts the container image pull and the 196 GB weight download at the same time rather than one after the other.
setup_node_mmh3.sh, on the nodeSHELL
#!/bin/bash# Per-node prep for MiniMax-H3 on a single 4x NVIDIA H100 box.# The Hugging Face cache goes to /ephemeral: root is only 96 GB, the checkpoints are not.set-usudomkdir-p /ephemeral/hf /ephemeral/media /ephemeral/out
sudochown-R ubuntu:ubuntu /ephemeral/hf /ephemeral/media /ephemeral/out
sudoapt-get update -qqsudoapt-get install -y-qq ffmpeg jqpython3-mpip install -q--break-system-packages huggingface_hub hf_transfer
# Pull the engine image and the weights at the same time: neither one waits for the other.nohupdocker pull lmsysorg/sglang:dev >/tmp/pull.log 2>&1&nohupenv HF_HOME=/ephemeral/hf HF_HUB_ENABLE_HF_TRANSFER=1 HF_TOKEN="$(cat ~/hf_token.txt)" \
python3-c"
from huggingface_hub import snapshot_download
snapshot_download('MiniMaxAI/MiniMax-H3',
allow_patterns=['model_index.json', 'FL2VA/*', 'Ref2VA/*'],
max_workers=16)
">/tmp/dl.log 2>&1&echo"image pull and checkpoint download started"
The download is the long pole. With hf_transfer enabled and sixteen workers it is bounded by the network rather than the disk. Check it landed before you launch anything.
Scope the download. The repository carries the original checkpoint and the diffusers format alongside each other. SGLang and vLLM both want the original layout, so restricting allow_patterns to model_index.json, FL2VA/* and Ref2VA/* is the difference between 196 GB and considerably more. If you only intend to run text-to-video and frame conditioning, drop Ref2VA/* and halve it again.
Step 6: Confirm the GPU Topology
Check the fabric before you serve. It takes one second, and it decides two environment variables in the next step.
Terminal, on the nodeSHELL
# What the driver reports about how the four cards are wired togethernvidia-smi topo -m# And what CUDA will actually permit between thempython3-c"
import torch
for i in range(4):
for j in range(4):
if i != j:
print(i, j, torch.cuda.can_device_access_peer(i, j))
"
If every pair reads PHB and can_device_access_peer reports False, as on ours, keep NCCL_P2P_DISABLE=1 and NCCL_CUMEM_ENABLE=0 in the launch below. If you see NV12 and True, you are on an NVLink flavour and can leave both out. The section above explains the difference.
Step 7: Launch MiniMax H3
The base SGLang image does not ship the diffusion extra, so it is installed from the bundled source at container start, which is exactly what the cookbook Docker form does. The two environment variables from step 6 go on the docker run line, and /ephemeral/media is mounted read-only so that file conditioning resolves later without any upload step.
serve_mmh3.sh, on the nodeSHELL
#!/bin/bash# Serve MiniMax-H3 on a single 4x NVIDIA H100 node.# The two NCCL variables suit a PCIe fabric; on an NVLink flavour you can leave them out.## Usage: serve_mmh3.sh [variant] variant = fl2va (default, serves t2va + fl2va) | ref2va
VARIANT=${1:-fl2va}
dockerrm-f mmh3 2>/dev/null ||truedocker run -d--name mmh3 --gpus all --shm-size 32g --ipc=host --network host \
--ulimit memlock=-1--ulimit stack=67108864 \
-v /ephemeral/hf:/root/.cache/huggingface \
-v /ephemeral/media:/data/minimax-h3:ro \
--env"HF_TOKEN=$(cat $HOME/hf_token.txt)" \
--env HF_HUB_ENABLE_HF_TRANSFER=1 \
--env NCCL_P2P_DISABLE=1 \
--env NCCL_CUMEM_ENABLE=0 \
lmsysorg/sglang:dev \
bash-lc'python -m pip install -e "/sgl-workspace/sglang/python[diffusion]" && exec sglang serve "$@"' -- \
--model-path MiniMaxAI/MiniMax-H3 \
--model-variant"$VARIANT" \
--num-gpus4 \
--tp-size4 \
--ulysses-degree1 \
--performance-mode speed \
--host 0.0.0.0 \
--port30010echo"launched MiniMax-H3 variant=$VARIANT on 4x NVIDIA H100"
Three flags shape what the server does. --model-variant fl2va serves both t2va and fl2va from one checkpoint partition, so nine of our eleven clips came from a single server. ref2va is a separate partition and needs a restart, which took about seven minutes including the diffusion extra install. --performance-mode speed deliberately keeps the DiT eager: the cookbook does not recommend torch.compile here because it changes numerical output for a negligible gain.
Step 8: Verify the Deployment
Confirm the pipeline end to end before committing four minutes to a full generation. Eight steps at four seconds is enough to exercise every stage.
Terminal, on the nodeSHELL
# Eight steps and four seconds, purely to prove the pipeline runs end to end
JOB=$(curl-s-X POST http://127.0.0.1:30010/v1/videos \
-H"Content-Type: application/json" \
-d'{"model": "MiniMaxAI/MiniMax-H3",
"prompt": "integrated_multimodal_description: [Shot 1] A violet ink drop blooms in clear water. The camera holds a static shot throughout.\noverall_soundscape: A single soft plink, then a quiet liquid swirl.\nnon_diegetic_music: One sustained synthesiser pad, no percussion.",
"seconds": 4, "task": "t2va", "conditions": [],
"target": {"short_edge": 768, "aspect_ratio": "1:1", "duration_seconds": 4.0},
"num_inference_steps": 8, "seed": 9233}'|jq-r .id)
until [ "$(curl -s http://127.0.0.1:30010/v1/videos/$JOB | jq -r .status)" = "completed" ]; dosleep4donecurl-s http://127.0.0.1:30010/v1/videos/$JOB/content -o /ephemeral/out/smoke.mp4
ffprobe -v error -show_entries stream=codec_name,width,height,sample_rate,channels,nb_frames \
-of csv=p=0 /ephemeral/out/smoke.mp4
ffprobe on the smoke test outputOUTPUT
h264,768,768,107
aac,32000,2,141
Two codecs, one file, 32000 Hz, two channels, back in 16.1 seconds. The 107 frames matter too: H3 quantises duration to a legal frame count, so a request for four seconds becomes 4.46 seconds.
Driving the Asynchronous Video Endpoint
H3 does not stream. A generation takes minutes, so SGLang exposes it as an OpenAI-shaped asynchronous job: submit, poll, then fetch the bytes. Three endpoints cover the whole surface.
Call
Returns
Notes
POST /v1/videos
{ "id": ... }
Accepts the prompt, the task, the target geometry and the sampler settings
GET /v1/videos/{id}
{ "status": ... }
Poll every few seconds until the status reaches completed
GET /v1/videos/{id}/content
The MP4 itself
One file, with the video and the audio already muxed together
The request body carries the decisions that shape the output. This is the exact shape we sent for every text-to-video clip.
Which conditioning path runs. The server has to be serving the matching partition
target.short_edge
768
The only self-hosted option. 2K needs H3-Regenerate-2K, which is not open
target.aspect_ratio
21:9 to 1:1
Sets the long edge from the short edge, and therefore the sequence length
num_inference_steps
50
The reference accuracy setting. This is the single biggest lever on wall clock
flow_shift
12.0
Video sampler shift, at the value the vLLM recipe gives as reference
audio_flow_shift
3.0
The audio stream gets its own shift, and it is not the same number
seed
One per clip
Fixed per clip, so a prompt edit can be attributed to the prompt
Conditioning is passed as a list. An empty list is text-to-video; an image with the keyframe role and frame_index: 0 anchors the first frame; a video or an image with the reference role drives Ref2VA. Every URI is a file:// path resolved inside the server container, which is why the media directory is bind mounted read-only at /data/minimax-h3.
mmh3_generate.py, condition constructionPYTHON
def build_conditions(task, spec):
"""MEDIA is the read-only mount inside the server container, /data/minimax-h3."""if task == "t2va":
return []
if task == "fl2va":
img = f"{MEDIA}/{spec['source']}_frame.png"return [{"type": "image", "uri": "file://" + img,
"role": "keyframe", "frame_index": 0}]
if task == "ref2va":
if spec.get("source_video"):
return [{"type": "video", "uri": f"file://{MEDIA}/{spec['source_video']}.mp4",
"role": "reference", "start_time_seconds": 0.0}]
return [{"type": "image", "uri": f"file://{MEDIA}/{spec['source']}_frame.png",
"role": "reference"}]
raise ValueError(task)
The client is straightforward: submit, poll on a four second interval, download, then probe the result with ffprobe and write a JSON record holding the exact request, the job id, the wall clock and the measured media properties. Those records are where every timing and every media property quoted here comes from.
mmh3_generate.py, one generation end to endPYTHON
BASE = "http://127.0.0.1:30010"def run_one(task, spec, steps):
body = {
"model": "MiniMaxAI/MiniMax-H3",
"prompt": spec["prompt"],
"seconds": spec["seconds"],
"task": task,
"conditions": build_conditions(task, spec),
"target": {"short_edge": 768, "aspect_ratio": spec["ratio"],
"duration_seconds": float(spec["seconds"])},
"num_outputs_per_prompt": 1,
"num_inference_steps": steps,
"flow_shift": 12.0,
"audio_flow_shift": 3.0,
"seed": spec["seed"],
}
t0 = time.time()
job = post("/v1/videos", body)
vid = job.get("id")
whileTrue:
time.sleep(4)
st = get(f"/v1/videos/{vid}")
status = st.get("status")
if status in ("completed", "succeeded"):
breakif status in ("failed", "cancelled", "error"):
return {"id": spec["id"], "error": st}
gen_s = time.time() - t0
mp4 = os.path.join(OUT, f"{spec['id']}.mp4")
with urllib.request.urlopen(f"{BASE}/v1/videos/{vid}/content", timeout=600) as r, \
open(mp4, "wb") as f:
f.write(r.read())
# ffprobe every file, so the record holds measured propertiesreturn {"id": spec["id"], "task": task, "generation_seconds": round(gen_s, 1),
"num_inference_steps": steps, "request": body, "job_id": vid,
"media": probe(mp4), "mp4": mp4}
💡
Probe every file as it lands. A generation that returns HTTP 200 has still told you nothing about whether the audio stream is present, stereo, or at the right sample rate. One ffprobe call per clip turns the whole run into evidence, and it costs nothing next to the four minutes that produced the file.
Prompting Is the Entire Quality Lever
Because H3-Context-IR is not part of the open release, nothing sits between what you type and what the transformer reads, so a vague prompt stays vague. MiniMax anticipated this and published the specification its own service writes to, as a set of prompt-writing skills in the MiniMax-H3 repository. Writing to that specification is not a style preference, it is the deployment.
The specification has two shapes. Text-to-video and frame conditioning take exactly three sections. Reference conditioning takes exactly six.
Every reference gets a label such as <Video 1> or <Picture 1>, and every label needs a retention marker
The four rules that carry the most weight
Sound is a first-class section, not an afterthought. Splitting diegetic sound from non-diegetic music is what lets the model place a bell motif in the score and a fabric rustle in the room at the same time.
Camera motion has a canonical phrasing: motion type, then amplitude, then speed. "The camera performs a tracking shot forward with medium amplitude at slow speed" is understood. "Slow dolly in" is a guess.
First-frame conditioning has a mandatory opening sentence. "For the target video, at 0.00 seconds into the target video, <Picture 1> (from [Shot 1]) is fully referenced." Without it the supplied frame is treated as loose inspiration rather than as frame zero.
Dialogue is tagged, and speakers are stable. The speaker is described outside the tag and the words go inside it with a language label, so the same identifier keeps the same voice across the clip.
Dialogue, in the official formPROMPT
A composed male presenter in his thirties (S1) says, <d>[English] Every frame you are
watching, and every sound, came out of one model in a single pass.</d>
Writing a brand palette into the pixels
Every clip here is in the Hyperstack palette, and none of it was colour graded afterwards. The palette was written into every prompt in natural language, so H3 rendered those colours directly. Hex codes mean nothing to a video model. Colour names do.
Brand token
Colour
What the prompt says
Primary purple
#9233e9
"electric violet"
Secondary indigo
#534ab7
"deep royal indigo"
Deep purple
#7c2bd0
"rich amethyst"
Bright accent
#bc3ae9
"luminous orchid"
Gradient
135deg
"a smooth 135-degree gradient running from electric violet at the upper left to deep royal indigo at the lower right"
Light tints
#f3eaff
"pale lilac-white bloom"
The clause that does the most work is the negative one. Every prompt ends its visual section with the same sentence: a strict colour palette of electric violet, deep royal indigo and rich amethyst, with luminous orchid highlights and pale lilac-white bloom; no warm oranges, no greens and no reds anywhere in frame. Stating what must not appear is what holds the palette steady across eleven independent generations with eleven different seeds.
The full prompt behind clip 01, in the official three-section formPROMPT
integrated_multimodal_description:[Shot 1] Abstract macro cinematography with a tight,
centred composition and shallow depth of field. A single sheet of weightless liquid silk
fills the frame against a seamless near-black background. Its surface carries a smooth
135-degree gradient running from electric violet at the upper left to deep royal indigo
at the lower right. The silk folds and unfurls continuously in slow motion; rich amethyst
shadows pool inside each fold while luminous orchid catch-lights trace the rolling edges,
and a pale lilac-white bloom haloes the brightest crests. The camera holds a static shot
throughout, letting the fabric move within the frame. Rendered with a strict colour
palette of electric violet, deep royal indigo and rich amethyst, with luminous orchid
highlights and pale lilac-white bloom; no warm oranges, no greens and no reds anywhere
in frame.
overall_soundscape: A soft, airy low-frequency swell of fabric moving through still air
in a large quiet room. A faint high shimmer rises each time the light catches a fold.
There are no voices and no mechanical noise.
non_diegetic_music: Minimal ambient electronic score: one slow sustained synthesiser pad
in a major key with a single soft bell motif every few seconds and gentle sub-bass, no
percussion.
📘
Abstract scenes only. MiniMax publishes a brand-promotion skill alongside the prompt-writing one, and it prohibits drawing logos, wordmarks and product interfaces. Everything here is motion, atmosphere and conceptual imagery, which is the correct way to generate brand material with a video model in any case: an approximated logo is worse than no logo.
Confirming the structure before you spend GPU time on it
The specification is easy to hold to once it is encoded. Two small helpers build the prompts in the official order, and a block of assertions checks every one of them at import, so the structure is confirmed in a second rather than inferred from the finished video four minutes later.
mmh3_prompts.py, prompt constructorsPYTHON
PALETTE = ("a strict colour palette of electric violet, deep royal indigo and rich amethyst, ""with luminous orchid highlights and pale lilac-white bloom; no warm oranges, ""no greens and no reds anywhere in frame")
# The official opener, required whenever a real first frame is supplied
FIRST_FRAME_ANCHOR = ("For the target video, at 0.00 seconds into the target video, ""<Picture 1> (from [Shot 1]) is fully referenced.")
def P(desc, sound, music):
"""T2VA and FL2VA: exactly three sections, in the official order."""return (f"integrated_multimodal_description: {desc}\n"f"overall_soundscape: {sound}\n"f"non_diegetic_music: {music}")
def R(subjects, summary, retention, detail, sound, music):
"""Ref2VA: exactly six sections, in the official order."""return (f"subject_definitions:\n{subjects}\n\n"f"summary:\n{summary}\n\n"f"retention_analysis:\n{retention}\n\n"f"detailed_description:\n{detail}\n\n"f"overall_soundscape:\n{sound}\n\n"f"non_diegetic_music:\n{music}")
mmh3_prompts.py, specification self-checkPYTHON
# Run at import, so the structure is confirmed before any GPU time is spent.for p in T2VA + FL2VA:
assert p["prompt"].startswith("integrated_multimodal_description:"), p["id"]
assert"\noverall_soundscape:"in p["prompt"], p["id"]
assert"\nnon_diegetic_music:"in p["prompt"], p["id"]
for p in REF2VA:
for section in ["subject_definitions:", "summary:", "retention_analysis:",
"detailed_description:", "overall_soundscape:", "non_diegetic_music:"]:
assert section in p["prompt"], (p["id"], section)
for p in FL2VA:
assert"<Picture 1>"in p["prompt"] and"0.00 seconds"in p["prompt"], p["id"]
This is the cheapest quality control in the whole pipeline. On a node costing $8.00 per hour, confirming the structure at import rather than after generation is worth roughly sixty cents of GPU time each time it runs.
Eleven Clips Across Three Task Modes
Every reference image and every reference clip used below came out of this same run. Nothing here is stock footage. After the seven text-to-video clips finished, we pulled one frame out of four of them at roughly forty per cent in, past the opening ramp where the composition has settled, and used those frames to drive the conditioned modes.
The chain: text to video, video to frame, frame back to video
Reference material generated by the same model on the same node, so it is photographic, already in palette, and free of any third-party licensing question.
The frame extraction step is a single ffmpeg call per clip, run on the node.
mmh3_extract_frames.sh, on the nodeSHELL
#!/bin/bash# Pull frames out of our own T2VA output, to use as reference material.# Frames are taken ~40% into each clip, past the opening ramp, where the composition# has settled. /ephemeral/media is mounted read-only in the server at /data/minimax-h3.set-u
OUT=/ephemeral/out
MEDIA=/ephemeral/media
mkdir-p"$MEDIA"
extract () {
local src="$OUT/$1.mp4"
local dur at
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0"$src")
at=$(python3-c"print(round(float('$dur') * 0.4, 2))")
ffmpeg -y-v error -ss"$at"-i"$src"-frames:v 1"$MEDIA/$1_frame.png"
}
for id in "$@"; do extract "$id"; done# Ref2VA video conditioning needs the clip itself visible inside the container toofor id in "$@"; docp-f"$OUT/$id.mp4""$MEDIA/$id.mp4"; done
The full run, in orderSHELL
# T2VA and FL2VA are both served by the fl2va checkpoint, so these run back to backpython3 mmh3_generate.py --task t2va
bash mmh3_extract_frames.sh 01_brand_hero 02_gpu_datacentre 03_data_viz 04_aurora
python3 mmh3_generate.py --task fl2va
# Ref2VA is a separate checkpoint partition, so the server has to be restartedbash serve_mmh3.sh ref2va
python3 mmh3_generate.py --task ref2va
Text to video and audio
Seven clips, no conditioning of any kind, straight from the three-section prompt. Each one has sound generated in the same pass as the picture, so unmute them.
Liquid gradient silk
TASKt2vaSIZE1344x768LENGTH6.58 sGEN272.2 s
A sheet of weightless silk carrying the 135-degree brand gradient, folding in slow motion against near-black. The soundtrack is a fabric swell with a soft bell motif over a sustained pad.
GPU data-centre aisle
TASKt2vaSIZE1344x768LENGTH6.58 sGEN272.2 s
A forward tracking shot with medium amplitude at slow speed down a symmetrical aisle. The volumetric haze and the reflected floor came from the prompt, not from a render pass.
Data visualisation materialising
TASKt2vaSIZE1344x768LENGTH6.58 sGEN272.2 s
An arc shot around glass bar-chart columns that grow in sequence. This clip later became the style reference for clip 11.
Violet aurora over dark peaks
TASKt2vaSIZE1536x672LENGTH8.00 sGEN356.2 s
The widest geometry in the set at 21:9 and eight seconds, which makes it the most expensive clip we generated. Note the aurora reflected in the lake, which no part of the prompt describes twice.
Violet ink bloom, macro
TASKt2vaSIZE768x768LENGTH5.17 sGEN104.1 s
Square, five seconds, and the cheapest clip in the set at 104.1 seconds. Fewer pixels and fewer frames mean a shorter sequence, and the denoising loop scales with it directly.
Refracting glass prisms
TASKt2vaSIZE1344x768LENGTH5.17 sGEN200.1 s
An arc shot with medium amplitude at slow speed around three prisms, with caustics thrown across the floor. The audio is glassy interface tones rather than music.
Speech, lip sync and a generated voice
The same three-section prompt, with one dialogue line tagged in the official form. H3 produced the speech, the lip movement and the voice timbre together with the picture, in the same pass, on the same 50 steps. Nothing here was dubbed.
TASKt2vaSIZE1344x768LENGTH6.58 sGEN276.2 s
A studio presenter delivering one tagged line. The voice, the lip sync and the rim lighting are all model output from a single prompt.
This clip cost 276.2 seconds against 272.2 for the three silent 16:9 clips of the same geometry. Adding speech, lip sync and a voice to a generation added four seconds, or about 1.5 per cent. The audio stream is cheap next to the video stream it is synchronised with.
First frame to video and audio
Both clips below start from a PNG pulled out of the text-to-video output above, passed as a keyframe condition at frame_index: 0, with the mandatory anchor sentence at the top of the prompt. The task is continuation: hold the composition, keep the motion going.
The hero clip continues from its own frame
TASKfl2vaSIZE1344x768LENGTH5.17 sGEN212.2 s
Conditioned on a frame taken 40 per cent into clip 01. The gradient keeps sliding and the folds keep forming from exactly where the reference left them.
The aurora continues from its own frame
TASKfl2vaSIZE1536x672LENGTH5.17 sGEN212.1 s
Conditioned on a frame from clip 04, at 21:9. The peaks hold as a static silhouette while the curtains keep moving, which is what the prompt asked for explicitly.
Reference to video and audio
Ref2VA is the most capable mode and the most expensive one. It is also a separate checkpoint partition, so the server has to be restarted with --model-variant ref2va before either of these will run. The prompt shape changes completely: six sections, labelled references, and a retention marker on every label saying how much of it should survive.
Video to video: clip 02, restyled
TASKref2vaSIZE1344x768LENGTH5.17 sGEN600.4 s
The data-centre aisle from clip 02 as a reference video, restyled into glowing monoliths. The forward camera motion and the aisle symmetry are preserved, the surfaces and the light are not.
The retention markers are doing the work here. <Video 1> is marked partially_preserved, which keeps the motion and the pacing. <Subject 1>, the racks, is marked attribute_transfer, which keeps their position and rhythm along the aisle while changing what they are made of. The markers are the dial: fully_preserved holds a reference exactly, weak_reference keeps only its atmosphere, and the two in between set how much of it carries across.
The full six-section prompt behind clip 10PROMPT
subject_definitions:<Video 1> is the source video for the editing task: a forward tracking shot down the
centre of a modern GPU data-centre aisle lit by vertical strips of electric violet light.
<Subject 1> is the double row of tall black server racks receding toward a vanishing
point in <Video 1>.
summary:
[video editing + reference generation] The target video restyles <Video 1> into a
futuristic night-time control room. <Subject 1> is transformed from server racks into
tall glowing monoliths while the original forward camera motion, aisle symmetry and
timing are preserved.
retention_analysis:<Video 1> (source video editing): partially_preserved - the forward tracking motion, the
symmetrical aisle layout and the overall pacing are retained, while the surfaces, haze
density and light intensity are restyled.
<Subject 1> (appears in [Shot 1]): attribute_transfer - the racks keep their position,
scale and rhythm along the aisle but take on the appearance of smooth glowing monoliths.
detailed_description:
The target video is in a sleek, high-contrast, photoreal science-fiction style with heavy
volumetric atmosphere.
[Shot 1] Cinematic wide symmetrical composition down the centre of a darkened control-room
corridor. The two facing rows from <Subject 1> now read as tall, smooth, edge-lit monoliths
whose vertical seams glow electric violet. Denser volumetric haze fills the corridor so each
seam casts a hard directional beam across the floor, and the polished surface below mirrors
the light into long deep royal indigo streaks. Scattered luminous orchid indicator points
pulse slowly along the monolith faces, and a pale lilac-white light source marks the far end
of the corridor. The camera performs a tracking shot forward with medium amplitude at slow
speed, matching the motion of <Video 1>. Rendered with a strict colour palette of electric
violet, deep royal indigo and rich amethyst, with luminous orchid highlights and pale
lilac-white bloom; no warm oranges, no greens and no reds anywhere in frame.
overall_soundscape:
A deep mechanical hum with a wide cavernous reverberation fills the corridor, layered with
a fine airy hiss of moving air and occasional low electronic pulses.
non_diegetic_music:
Low pulsing synth ostinato with sparse metallic percussion, darker and more spacious than
the source.
Image reference: a style carried onto a new subject
TASKref2vaSIZE1344x768LENGTH5.17 sGEN312.2 s
A frame from clip 03 supplied as a style reference only, marked weak_reference. The bar-chart geometry does not appear: the glass material, the colour system and the particle atmosphere carry across to a network graph instead.
weak_reference is the marker that makes this work. It tells the model to take the material treatment, the lighting and the colour system, and to leave the subject behind. The same image marked fully_preserved would have produced bar charts again.
Verifying all eleven
One loop over the output directory confirms what every file contains.
Terminal, on the nodeSHELL
for f in /ephemeral/out/*.mp4; doecho "$(basename $f) -> $(ffprobe -v error \
-show_entries stream=codec_name,width,height,r_frame_rate,nb_frames,sample_rate,channels \
-of csv=p=0"$f"| tr '\n'' ')"
done
Every file is H.264 and AAC, at 24 frames per second, 32000 Hz, two channels. Look at that column of frame counts: 158, 192 and 124 are all of the form 17n + 5, which is exactly the frame quantisation the vLLM recipe documents. A request for six seconds becomes 158 frames, which is 6.58 seconds, and the extra 0.58 seconds is the model rounding up to the nearest legal length.
Where the Generation Time Goes
Eleven clips, 3,090.1 seconds of generation, 65.3 seconds of finished video. That is about 47 seconds of four-GPU wall clock for every second you end up with. The server log shows exactly where that time goes.
Two Ref2VA jobs on the same server, minutes apart. Encoding, latent preparation and VAE decoding barely register against the denoising loop.
Stage timings copied from the SGLang server log. Both jobs ran 50 inference steps at 1344x768.
Denoising is 98.6 per cent of server-side time in both jobs. Everything else together is under nine seconds. That leaves two settings worth tuning: the number of inference steps, and the length of the sequence being denoised. Nothing else moves the needle.
The two jobs also differ by exactly one thing, and it shows. The video-reference job ran at 11.75 seconds per step; the image-reference job, at the same resolution and the same step count, ran at 6.08. A reference video is decoded, encoded and then carried in the attention sequence for every one of the 50 steps, so it nearly doubles the per-step cost. It also lifts peak memory from 46,020 MB to 54,888 MB.
Sequence length is the price, not resolution
Two of the clips isolate frame count directly. A 1344x768 frame and a 1536x672 frame contain exactly the same 1,032,192 pixels, so the only difference between clip 01 and clip 04 is the frame count: 158 against 192. Twenty-two per cent more frames cost thirty-one per cent more time.
Generation time against the size of the output
Four text-to-video clips, all at 50 steps. The bars are measured wall clock. The line is the same numbers divided by the megapixel-seconds of finished video, and it climbs.
Every figure measured on 4x NVIDIA H100 80GB PCIe. Megapixel-seconds is width x height x duration, which tracks the visual token count the transformer has to attend over.
If cost were linear in sequence length that line would be flat. It climbs from 34.1 to 43.1 seconds per megapixel-second instead, because this release ships inference with full attention only: the initial open source drop is full attention, and the sparse attention implementation MiniMax trained with is published separately at a later date. Until it lands, doubling the length of a clip costs more than twice as much.
What each task mode costs
The four clips below are the cleanest comparison in the run: identical geometry at 1344x768, identical duration at 5.17 seconds, identical 50 steps. Only the conditioning path changes.
The same clip length through four different conditioning paths
Frame conditioning is nearly free. An image reference costs half as much again. A reference video triples it.
Clips 07, 08, 11 and 10 respectively, all at 1344x768 and 5.17 seconds with 50 inference steps.
Task
Conditioning
Generation
Against text to video
t2va
None
200.1 s
baseline
fl2va
One PNG at frame zero
212.2 s
+6 per cent
ref2va
One reference image
312.2 s
+56 per cent
ref2va
One reference video
600.4 s
3.0x
💡
Budget reference video carefully. Ref2VA accepts up to three clips totalling fifteen seconds. Our single five second reference already tripled the cost of the generation. Plan a Ref2VA batch around that multiplier rather than around the text-to-video numbers, and keep reference clips as short as the shot allows.
How four NVIDIA H100 GPUs compare
The vLLM recipe publishes measured end-to-end figures for the same model on other four-GPU configurations. Normalising all of them to megapixel-seconds of finished video makes them roughly comparable, with the caveat that the NVIDIA B300 figure is a first-frame job at 1248x768 while the other two are text to video at 1344x768.
Four GPUs, three generations of hardware
Lower is faster. This is wall clock divided by the megapixel-seconds of video produced, so clips of different lengths can sit on the same axis.
NVIDIA B300 and AMD Instinct MI300X figures from the official vLLM recipe. The NVIDIA H100 figure is our own, on the SGLang path at 50 steps.
NVIDIA H100 is roughly four times the wall clock of NVIDIA B300 on this workload, which is what you would expect from a diffusion transformer that is almost entirely attention and feed-forward compute in BF16. It is also a good deal cheaper per GPU hour, at $2.00 on Spot against $7.40 for NVIDIA B300 on demand, so the cost per finished clip is much closer than the latency suggests. If you want faster iteration on NVIDIA H100 rather than faster hardware, the lever is step count: there is a community Turbo LoRA that distils first-and-last-frame generation down to four steps instead of fifty, with a distillation recipe behind it. We ran the full 50-step path throughout, on the released weights as published.
What the Run Cost
Four NVIDIA H100 80GB PCIe cards on Spot are $2.00 per GPU per hour, so the node runs at $8.00 per hour, or 0.22 cents per second. That makes the cost of every clip a simple multiplication.
What each clip cost to generate
Generation time multiplied by $8.00 per hour. This is GPU time only, and excludes the download and the two server starts.
CHEAPEST CLIP
$0.23
104.1 s, the square macro clip at 768x768
TYPICAL 16:9 CLIP
$0.60
272.2 s, six seconds at 1344x768
MOST EXPENSIVE CLIP
$1.33
600.4 s, the video-to-video restyle
ALL ELEVEN CLIPS
$6.87
3,090.1 s of generation across three task modes
Rates from the Hyperstack GPU pricing page at the time of the run. Spot capacity can be reclaimed, so a long batch should checkpoint its results to somewhere other than the node.
The session as a whole is a different number, because the 196 GB download and two server starts are on the clock as well. End to end, from an empty node to eleven verified clips, took about two hours.
Phase
Wall clock
Node cost
Deploy the node from the dashboard and prepare the disks
about 3 min
$0.40
Pull the container image and 196 GB of weights
about 48 min
$6.40
Start the server, twice, once per checkpoint partition
about 14 min
$1.87
Generate eleven clips, measured
51.5 min
$6.87
Total, end to end
about 2 hours
about $16
The download is the single largest line, and it is the one that does not get cheaper on faster GPUs. Everything after it is minutes, which means the marginal cost of another clip is measured in cents once the node is warm.
💡
Copy each clip off as it lands. The ephemeral disk is runtime storage that lives with the virtual machine, so treat /ephemeral/out as a working directory and pull finished clips down with scp as you go. It also keeps the review loop tight, because you can watch clip 03 while clip 04 is still generating.
Why Deploy MiniMax H3 on Hyperstack?
Hyperstackis a cloud platform built for AI andmachine learningworkloads. Here is why it suits a generative video model specifically:
Four NVIDIA H100 GPUs on One Node H3 needs about 135 GiB resident for one task partition, which is four cards, and it needs them on one machine. The NVIDIA H100 80GB PCIe flavour gives exactly that, with 124 vCPUs and 720 GB of RAM behind it for the decode path.
3.2 TB of NVMe Where the Weights Go Two checkpoint partitions and a container image do not fit on a 96 GB root disk. The ephemeral NVMe disk at /ephemeral took the 196 GB download, the media directory and every generated MP4 with room to spare.
Matched CUDA, Driver and Docker Images The Ubuntu 24.04 R570 CUDA 12.8 with Docker image ships the driver and the container runtime already matched to the SGLang image, so the deployment goes from ssh to docker run with no driver work at all.
Spot Pricing and Per-Minute Billing Spot VMs put this node at $8.00 per hour, and billing is per minute of runtime. A full evaluation run of a video model costs less than a working lunch.
A Choice of Interconnect This deployment runs on the PCIe flavour, with NCCL configured to match it. When peer-to-peer matters, NVIDIA H100 SXM and the NVLink flavours are one flavor_name away, with no other change to the launch command.
A Route to Faster Hardware Video diffusion rewards newer silicon more than language models do. NVIDIA H200 SXM at 141 GB per card and NVIDIA Blackwell reservations take the same launch command onto hardware where the same clip takes a quarter of the time.
Generate video with native audio
Run MiniMax H3 on four NVIDIA H100 GPUs
One node on Spot at $8.00 per hour. Our run pulled 196 GB, served both checkpoint partitions and produced eleven clips with synchronised stereo audio for under $16.
4x NVIDIA H100 80GBTP 4 x Ulysses 1768p with 32 kHz stereo$8.00 per hour
Launch an NVIDIA GPU node on Hyperstack today.
FAQs
What hardware do you need to run MiniMax H3?
One task partition is about 135 GiB in BF16, so four GPUs of 80 GB is the practical floor. We ran it on 4x NVIDIA H100 80GB PCIe on a single Hyperstack node, peaking at 46.0 to 54.9 GB per GPU with tensor parallel 4 and Ulysses degree 1.
Can you get 2K video out of the open MiniMax H3 weights?
No. 2K comes from H3-Regenerate-2K, which is not part of the open release, so a self-hosted deployment produces a 768 pixel short edge. Every published aspect ratio from 21:9 to 9:16 works, and sets the long edge from there.
How long does MiniMax H3 take to generate a video on NVIDIA H100?
On 4x NVIDIA H100 80GB at 50 inference steps we measured 104.1 seconds for a five second square clip, 272.2 seconds for six seconds at 1344x768, and 600.4 seconds with a reference video. Denoising is 98.6 per cent of that.
How much does it cost to run MiniMax H3 on Hyperstack?
The 4x NVIDIA H100 80GB PCIe Spot node is $8.00 per hour, or 0.22 cents per second. The eleven clips cost $6.87 of GPU time between them, and the whole session including the 196 GB download came to about $16.
How do you write a MiniMax H3 prompt without H3-Context-IR?
Follow the prompt-writing skill in the model repository. Text-to-video and frame conditioning take exactly three sections in order: integrated_multimodal_description, overall_soundscape and non_diegetic_music. Reference conditioning takes six, and every labelled reference needs a retention marker.