Local Inference Server

The Vi SDK ships an OpenAI-compatible inference server you can run on your own hardware. It serves any architecture the SDK's loaders support, applies LoRA adapter weights, and speaks the same /v1/chat/completions protocol as the OpenAI API, so existing OpenAI client code can point at it with only a base URL change.

Before You Start

Get started with the Vi SDK →

Local Server Or NIM?

The local server runs any supported architecture with LoRA adapters applied, using the SDK's own loaders. NVIDIA NIM offers prebuilt GPU-optimized containers, but only for Cosmos-Reason1 and Cosmos-Reason2, and it does not apply PEFT adapters. Use the local server for everything else.


Installation

Terminal
pip install vi-sdk[deployment]

Start the server

python -m vi.deployment.local.server \
  --pretrained-model-name-or-path nvidia/Cosmos-Reason2-2B \
  --port 8000
python -m vi.deployment.local.server \
  --pretrained-model-name-or-path /path/to/model \
  --port 8000
python -m vi.deployment.local.server \
  --run-id abc123 \
  --secret-key your-key \
  --organization-id your-org \
  --port 8000
python -m vi.deployment.local.server \
  --pretrained-model-name-or-path nvidia/Cosmos-Reason2-2B \
  --model-name my-vision-model \
  --port 8000

Command-line arguments

Name
Type
Description
Required
Default
--pretrained-model-name-or-path
string
HuggingFace model ID or path to a local model directory.
Optional
--run-id
string
Datature run ID, to serve a model trained on the platform.
Optional
--secret-key
string
Datature API secret key. Required with --run-id.
Optional
--organization-id
string
Datature organization ID. Required with --run-id.
Optional
--model-name
string
Model identifier clients use in requests. Defaults to one auto-generated from the model path or run ID.
Optional
--host
string
Bind address.
Optional
0.0.0.0
--port
integer
Bind port.
Optional
8000
The Model Loads Eagerly

Weights load during startup, not on the first request. The server does not report healthy until the model is resident, so a large checkpoint means a slow first boot. This is expected.


Endpoints

Endpoints

Method
Path
Purpose
GET
/health
Readiness check; reports available models or an error
GET
/v1/health
Same as /health, under the v1 prefix
GET
/v1/models
List servable model identifiers
POST
/v1/chat/completions
Generate a completion, optionally streamed
curl http://localhost:8000/health
curl http://localhost:8000/v1/models
Use The Exact Model ID

The model field in a request must match an ID returned by /v1/models, or the server responds 404. When in doubt, query /v1/models first rather than guessing from the path you passed on the command line.


Request formats

The server accepts two input shapes. Send one or the other. A request carrying both returns 400.

OpenAI format

Standard messages, with images as content parts.

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Cosmos-Reason2-2B",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "image_url", "image_url": {"url": "/path/to/image.jpg"}},
        {"type": "text", "text": "Describe this image"}
      ]
    }]
  }'
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-used")

response = client.chat.completions.create(
    model="nvidia/Cosmos-Reason2-2B",
    messages=[{
        "role": "user",
        "content": [
            {"type": "image_url", "image_url": {"url": "/path/to/image.jpg"}},
            {"type": "text", "text": "Describe this image"},
        ],
    }],
)

print(response.choices[0].message.content)

OpenAI format fields

Name
Type
Description
Required
Default
messages
array
Chat messages. For vision requests, 'content' must be an array of parts containing an 'image_url' part alongside the 'text' part.
Required

Vi SDK format

A flatter shape for the common single-image case.

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Cosmos-Reason2-2B",
    "source": "/path/to/image.jpg",
    "user_prompt": "Describe this image"
  }'

Vi SDK format fields

Name
Type
Description
Required
Default
source
string
Image source: a file path, a URL, or a base64 data URI such as 'data:image/jpeg;base64,...'.
Required
user_prompt
string
Text prompt accompanying the source.
Optional
Most Common Mistake

Passing a plain string as content with no image. Vision models need the image in the multimodal content array. You can also use the Vi SDK source field, which is harder to get wrong.


Generation parameters

Parameters

Name
Type
Description
Required
Default
model
string
Model identifier, matching an entry from /v1/models.
Required
temperature
float
Sampling temperature, 0.0 to 2.0.
Optional
0.7
max_tokens
integer
Maximum tokens to generate.
Optional
None
stream
boolean
Stream the response as server-sent events.
Optional
false
top_p
float
Nucleus sampling threshold, 0.0 to 1.0.
Optional
None
top_k
integer
Top-k sampling parameter.
Optional
None
seed
integer
Random seed for reproducible generation.
Optional
None
repetition_penalty
float
Repetition penalty.
Optional
None
frequency_penalty
float
OpenAI-style frequency penalty.
Optional
None
presence_penalty
float
OpenAI-style presence penalty.
Optional
None
stop
string | array
Stop sequences.
Optional
None
logit_bias
object
Per-token logit adjustments.
Optional
None
response_format
object
Structured output schema, for example {'type': 'json_schema', 'json_schema': {...}}.
Optional
None
generation_config
object
Advanced generation parameters, merged with the individual fields above.
Optional
None

Streaming

Set stream: true to receive server-sent events.

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-used")

stream = client.chat.completions.create(
    model="nvidia/Cosmos-Reason2-2B",
    messages=[{
        "role": "user",
        "content": [
            {"type": "image_url", "image_url": {"url": "/path/to/image.jpg"}},
            {"type": "text", "text": "Describe this image"},
        ],
    }],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Cosmos-Reason2-2B",
    "source": "/path/to/image.jpg",
    "user_prompt": "Describe this image",
    "stream": true
  }'

Structured outputs

Pass a JSON schema in response_format to constrain generation to it.

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-used")

response = client.chat.completions.create(
    model="nvidia/Cosmos-Reason2-2B",
    messages=[{"role": "user", "content": [
        {"type": "image_url", "image_url": {"url": "/path/to/image.jpg"}},
        {"type": "text", "text": "Extract the reading"},
    ]}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "gauge_reading",
            "schema": {
                "type": "object",
                "properties": {
                    "value": {"type": "number"},
                    "unit": {"type": "string"},
                },
                "required": ["value", "unit"],
            },
        },
    },
)

print(response.choices[0].message.content)

Embedding the server in Python

create_server() returns a configured FastAPI app you can run yourself or mount into a larger application.

import uvicorn
from vi.deployment.local.server import create_server

app = create_server(pretrained_model_name_or_path="nvidia/Cosmos-Reason2-2B")

uvicorn.run(app, host="0.0.0.0", port=8000)
from vi.deployment.local.server import create_server

app = create_server(
    run_id="abc123",
    secret_key="your-key",
    organization_id="your-org",
)

create_server() parameters

Name
Type
Description
Required
Default
backend
InferenceBackend
Pre-configured backend instance. Takes precedence over the model arguments.
Optional
None
pretrained_model_name_or_path
string
HuggingFace model ID or local model directory.
Optional
None
run_id
string
Datature run ID.
Optional
None
secret_key
string
Datature API secret key.
Optional
None
organization_id
string
Datature organization ID.
Optional
None
model_name
string
Model identifier used in requests. Auto-generated when omitted.
Optional
None

Returns: a configured FastAPI application.


Custom backends

The server talks to models through the InferenceBackend interface, so you can swap in vLLM, TGI, a remote API, or your own stack. The bundled default is ViBackend.

from collections.abc import AsyncIterator
from typing import Any

from vi.deployment.local.backends.base import GenerationResult, InferenceBackend


class MyBackend(InferenceBackend):
    def available_models(self) -> list[str]:
        return ["my-model"]

    async def generate(
        self,
        messages: list[dict] | None,
        response_format: Any | None = None,
        generation_config: dict | None = None,
        **kwargs: Any,
    ) -> GenerationResult:
        text = await self._run_one_shot(messages)
        return GenerationResult(
            text=text,
            prompt_tokens=0,
            completion_tokens=0,
            finish_reason="stop",
        )

    async def generate_stream(
        self,
        messages: list[dict] | None,
        response_format: Any | None = None,
        generation_config: dict | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[str]:
        async for token in self._stream(messages):
            yield token
from vi.deployment.local.server import create_server

app = create_server(backend=MyBackend())
Do Not Block The Event Loop

All four backend methods are async. If your runtime is synchronous and GPU-bound, as the bundled ViBackend is, wrap the call in asyncio.to_thread(...), or a long generation will stall every other in-flight request.

To serve Vi SDK format requests natively, also override generate_vi_format() and generate_stream_vi_format(). The defaults route through OpenAI-format conversion. generate_stream* must yield strings; the server skips None silently.


Troubleshooting

The model field must match an ID from /v1/models exactly. Query that endpoint and copy the ID verbatim. It is not always the path you passed to --pretrained-model-name-or-path.

Weights load eagerly at startup, so a large checkpoint takes time before /health reports healthy. If it never does, check the startup logs. A load failure surfaces there, not in the health response.

You sent both messages and source/user_prompt. Pick one shape and remove the other.

A plain string content carries no image. Use the multimodal content array with an image_url part, or switch to the Vi SDK source field.

The bundled backend runs generation on the GPU one request at a time. For higher concurrency, serve behind a queue or implement a batching InferenceBackend.


Related resources

Vi SDK Inference

Run predictions in-process with ViModel instead of over HTTP.

Vi SDK NIM

Deploy Cosmos-Reason models as NVIDIA NIM containers.

Deployments API

Manage platform-hosted serving instances and endpoints.

Download A Model

Export trained weights to serve them yourself.


Did this page help you?