Status: env-override path is fully working today on either branch; code-level swap pending (Roadmap → 🚧 Local inference branch). The
local-inferencebranch exists as a placeholder and currently mirrorsmainbyte-for-byte. The hosting steps below work today against any OpenAI-compatible endpoint by settingRESEARCH_LLM_API_BASEon either branch — that path is the supported way to run local inference right now. The dedicated branch with default-config-aware swaps (less env wiring, local-first defaults baked intosrc/llm_client.py) is still in development. Track progress in the Roadmap section of README.md.
The default setup calls Qwen3-30B-A3B-Thinking on OpenRouter — fastest path, no GPU needed, pay-per-call. Once the planned local-inference divergence lands, that branch will swap the OpenRouter client for a generic OpenAI-compatible client so the env wiring below collapses to defaults.
This guide covers when to choose local inference, how to host Qwen3-30B-A3B-Thinking (or another reasoning model), and how to wire it back to the synthesis pipeline.
- On-prem requirement — data must not leave your network
- Cost predictability — your usage volume makes per-call pricing more expensive than amortized GPU hosting
- Latency floor — round-trip to OpenRouter adds 100–500 ms; local hosting can drop that to single-digit ms
- GPU you already have — repurposing existing infra
- Custom fine-tune — running your own variant of Qwen3/DeepSeek/Llama
Otherwise, default OpenRouter mode is simpler.
Qwen3-30B-A3B-Thinking needs:
- ~60 GB VRAM at FP16
- ~30 GB VRAM at INT8
- ~16 GB VRAM at INT4 (with quality tradeoff)
Single-GPU friendly options:
- 1× A100 80 GB (FP16, comfortable headroom)
- 1× H100 80 GB
- 2× RTX 4090 24 GB (with tensor parallelism, INT8)
- 1× RTX 6000 Ada 48 GB (INT8)
Multi-GPU:
- 2× A100 40 GB (FP16, tensor-parallel)
- 4× RTX 3090 24 GB (FP16, tensor-parallel)
If you have less, drop down a model tier (DeepSeek-R1-Distill-Qwen-14B, Qwen3-14B, Qwen-QwQ-32B at INT4) — the synthesis pipeline is model-agnostic.
cd gigaxity-deep-research
git checkout local-inference # exists today, currently identical to main
pip install -e .Right now the branch is a packaging placeholder — it mirrors main byte-for-byte so you can pin downstream tooling to it without a code-level divergence. Once the planned divergence lands, the branch will differ from main in src/llm_client.py (generic OpenAI-compatible client instead of OpenRouter-flavored) and the default RESEARCH_LLM_API_BASE. Everything else (search, fusion, synthesis, citations) will stay identical.
Today, run the same setup against either branch by pointing the LLM client at your local endpoint. RESEARCH_LLM_API_KEY must be non-empty (see the note in the Configure section below); set any placeholder string when your model server doesn't enforce auth:
RESEARCH_LLM_API_BASE=http://localhost:8000/v1 \
RESEARCH_LLM_API_KEY=local-anything \
RESEARCH_LLM_MODEL=Qwen/Qwen3-30B-A3B-Thinking-2507 \
python run_mcp.pyOpenRouter-specific behavior (per-request X-OpenRouter-Api-Key header passthrough) is harmless against most OpenAI-compatible servers.
vLLM is the highest-throughput option for OpenAI-compatible serving.
pip install vllm
# Single-GPU FP16
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-30B-A3B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000 \
--max-model-len 32768
# Multi-GPU tensor-parallel
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-30B-A3B-Thinking-2507 \
--tensor-parallel-size 2 \
--host 0.0.0.0 \
--port 8000
# Quantized (INT4)
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen3-30B-A3B-Thinking-2507-AWQ \
--quantization awq \
--host 0.0.0.0 \
--port 8000vLLM exposes /v1/chat/completions at the OpenAI-compatible path.
SGLang is faster for multi-turn / structured generation workloads and has built-in support for reasoning models.
pip install "sglang[all]"
python -m sglang.launch_server \
--model-path Qwen/Qwen3-30B-A3B-Thinking-2507 \
--host 0.0.0.0 \
--port 8000For modest GPUs (24 GB) or Apple Silicon, pull a quantized GGUF build of Qwen/Qwen3-30B-A3B-Thinking-2507 from a community quanter — browse the available GGUF quants on HuggingFace and pick a repo that publishes the full static ladder (Q2_K through Q8_0; Q4_K_M ≈ 18.7 GB for this 30B-A3B architecture). Serve the GGUF with any runtime that loads it (llama.cpp's llama-server, vLLM with --quantization gguf, LM Studio, or Jan) — each exposes an OpenAI-compatible endpoint that the orchestrator can talk to.
In .env:
# vLLM / SGLang / llama.cpp / any OpenAI-compatible GGUF runtime
RESEARCH_LLM_API_BASE=http://localhost:8000/v1
RESEARCH_LLM_API_KEY=local-anything # placeholder string — see note below
RESEARCH_LLM_MODEL=Qwen/Qwen3-30B-A3B-Thinking-2507RESEARCH_LLM_API_KEY must be non-empty because every entrypoint calls settings.require_llm_key() and fails fast on an empty key — this is the OpenRouter-mode safety check that prevents the server from coming up without a configured key. For local servers that do not enforce auth, set the variable to any placeholder string (local-anything, na, etc.). If your model server uses bearer tokens, set this to the actual token value.
curl -X POST http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-30B-A3B-Thinking-2507",
"messages": [{"role":"user","content":"hello"}],
"max_tokens": 64
}'If you get a JSON response with choices[0].message.content, the model server is healthy.
Then start the orchestrator:
python run_mcp.py < /dev/null # MCP mode
# or
uvicorn src.main:app --port 8001 # REST mode (port 8001 to avoid clashing with the model server on 8000)When the model server is on a different machine than the orchestrator:
[GPU box] → vLLM / SGLang on 192.0.2.50:8000
▲
│
│ HTTPS or HTTP over private network
│
[Edge / app server] → Gigaxity Deep Research orchestrator
RESEARCH_LLM_API_BASE=http://192.0.2.50:8000/v1
If crossing a public network, terminate TLS on the model server and use a bearer token. Don't expose vLLM/SGLang directly to the internet without auth — there's no rate-limiting, no token accounting, and no auth middleware in the default OpenAI-compatible servers.
RESEARCH_LLM_MODEL is read at request time, not startup. To switch from one model to another (say to DeepSeek-R1), change the env var and restart the orchestrator. The model server has to be hosting the requested model, of course.
For multi-model serving, run multiple model servers on different ports and have multiple orchestrator instances pointed at different RESEARCH_LLM_API_BASE values, registered under different MCP aliases.
| Symptom | Cause | Fix |
|---|---|---|
ConnectionError on first call |
Orchestrator can't reach model server | Verify RESEARCH_LLM_API_BASE; curl <base>/models from the orchestrator host |
| 401 from model server | Bearer token mismatch | Set RESEARCH_LLM_API_KEY to match what the server expects |
| Empty completions | Model not loaded yet | vLLM/SGLang takes 30–120 s to load 30B; wait or check the model-server logs |
| Out-of-memory at startup | Model larger than VRAM | Switch to a quantized variant (AWQ, INT4) or smaller model |
| Slow first request | Cold-start prompt eval | Send a warmup request after model load before traffic |
- REST API setup — for distributed deployments
- Configuration reference — full env var list