One-command installation of a local Qwen3.8-27B coding stack on an NVIDIA RTX 4090:
- NInfer compiled natively for Linux/WSL and Ada
sm_89 - CUDA Toolkit 13.1
- the verified Qwen3.8-27B
.ninfermodel - Oh My Pi (OMP) running inside WSL
- a
ninfercommand that starts the service and opens OMP
This installer deliberately uses a conservative WSL profile rather than NInfer's maximum-context shipping profile. The goal is repeatable daily use on a 24 GB RTX 4090 even when Windows and WSL GPU-memory availability drifts.
Run this inside an Ubuntu WSL2 terminal:
curl -fsSL https://raw.githubusercontent.com/pojans/ninfer-4090-setup/main/install.sh | shThe installer provisions everything, starts NInfer, waits for the API to become healthy, and launches the OMP TUI with Qwen3.8 27B (NInfer) · xhigh selected.
To provision without starting NInfer or opening OMP:
curl -fsSL https://raw.githubusercontent.com/pojans/ninfer-4090-setup/main/install.sh | sh -s -- --no-launch- Windows 11 with WSL2
- an Ubuntu WSL distribution with
apt-get - NVIDIA GeForce RTX 4090 with 24 GB VRAM
- a current Windows NVIDIA driver with WSL CUDA support
- at least 30 GB free under the WSL home directory for a clean installation
- internet access to GitHub, NVIDIA's package repository, and Hugging Face
sudoaccess inside WSL
The CUDA toolkit is installed inside WSL. Do not install a Linux display driver in WSL; GPU access is supplied by the Windows NVIDIA driver.
ninferninfer checks http://127.0.0.1:8080/v1/models. If the endpoint is not healthy, it starts ninfer-serve in the background and waits up to ten minutes for model loading. It then launches OMP in the same terminal.
Other commands:
ninfer serve # Start the service without opening OMP
ninfer status # Show endpoint state and GPU memory usage
ninfer stop # Stop NInfer and release its VRAM
ninfer --help # Show command helpService logs are written to:
~/.local/state/ninfer/server.log
- Rejects native Windows, native Linux, non-RTX-4090 GPUs, and insufficient disk space.
- Installs missing packages:
build-essential,cmake,ninja-build,git,curl,ca-certificates, andprocps. - Installs the NVIDIA WSL CUDA 13.1 toolkit if
/usr/local/cuda-13.1/bin/nvccis missing. - Clones the
rtx4090-portbranch ofsergiuszm/ninfer-4090and pins a fresh checkout to verified commit981b685ea2124fdaed023123d2e63fd29d529ab8. - Builds
ninfer-servein Release mode with Ninja and all available CPU cores. - Downloads the model from pinned Hugging Face revision
18dfc887423fa5aabf3cb56fac41490e462b3fab. - Verifies the model's exact size:
18,210,531,328bytes (16.96 GiB). Interrupted downloads resume with HTTP ranges. - Installs
~/.local/bin/ninferand registers~/.local/binin~/.bashrc. - Installs the prebuilt Linux x64 OMP v18.0.4 binary to
~/.local/bin/omp. - Writes the NInfer provider to
~/.omp/agent/models.ymland selects it in~/.omp/agent/config.yml. - Starts the service and launches OMP unless
--no-launchwas passed.
The generated ninfer command launches this server:
ninfer-serve qwen3_8_27b.ninfer \
--host 127.0.0.1 \
--port 8080 \
--max-context 114688 \
--kv-capacity 114688 \
--max-concurrency 1 \
--max-pending-requests 16 \
--pending-timeout-ms 600000 \
--prefill-chunk 512 \
--kv-dtype int8 \
--spec mtp \
--draft-tokens 3 \
--lm-head-draft \
--no-cuda-graph| Flag | Effect | Why this profile uses it |
|---|---|---|
qwen3_8_27b.ninfer |
Loads the official groupwise .ninfer Qwen3.8-27B artifact. |
The artifact contains the model identity and the target/MTP weights expected by this NInfer fork. |
--host 127.0.0.1 |
Listens only on the WSL loopback interface. | OMP runs in the same WSL distribution. Local binding avoids exposing an unauthenticated model API to the LAN. |
--port 8080 |
Serves the API on TCP port 8080. | Matches OMP's configured base URL, http://127.0.0.1:8080/v1. |
--max-context 114688 |
Caps each request's logical sequence length at 114,688 tokens. | Leaves practical VRAM margin below the RTX 4090 fork's measured INT8 ceiling. This is intentional WSL stability headroom, not the largest context NInfer can theoretically fit. |
--kv-capacity 114688 |
Allocates a shared Main Text KV page pool for 114,688 tokens. | Equal to the per-request ceiling. With one admitted request, the active session can use the entire pool without relying on automatic sizing from momentary free VRAM. |
--max-concurrency 1 |
Admits one active generation request. | OMP is used as one interactive coding session. One lane minimizes fixed sequence state and avoids extra graph/lane allocations. The upstream fork measures concurrency 2 as useful, but it costs additional VRAM and prefill still serializes. |
--max-pending-requests 16 |
Allows 16 additional generation requests to wait behind the active request. | Prevents short bursts from immediately returning overload errors while keeping the queue bounded. Total generation lifetime capacity is active plus pending requests. |
--pending-timeout-ms 600000 |
Gives preparation and admission ten minutes before returning request_queue_timeout. |
Deep prompts can spend longer than the server's 30-second default waiting behind prefill. The longer deadline is safer for agent/tool workloads. |
--prefill-chunk 512 |
Processes prompt prefill in 512-token units. The value must be a positive multiple of 128. | A smaller conservative chunk bounds each prefill unit and its planned scratch requirement. Upstream benchmarks usually use 1024; 512 is the profile verified stable under WSL on this machine. |
--kv-dtype int8 |
Stores the KV cache in group-64 INT8 form. | Higher cache precision than the fork's 4-bit and 2-bit E8 modes, at the cost of less maximum context. This profile chooses precision and predictable behavior over the 262K compressed-KV headline. |
--spec mtp |
Enables Multi-Token Prediction speculative decoding. | NInfer can draft several tokens and verify them with the target model, substantially increasing decode throughput on predictable output such as code. |
--draft-tokens 3 |
Gives MTP three draft positions. | MTP3 is the fork's measured Qwen3.8-27B profile. Acceptance varies by content: structured code tends to accept more drafts than prose. |
--lm-head-draft |
Selects the optimized proposal head for speculative decoding. | Avoids the full proposal head and is the measured companion to MTP3. It requires a speculative backend. |
--no-cuda-graph |
Disables CUDA Graph decode replay. | Trades some decode speed for lower graph-allocation/capture sensitivity. Windows and WSL consume variable portions of the shared 4090 VRAM; disabling graphs was the reliable choice for this daily-driver profile. |
The installer does not implement the GPU kernels; it selects a verified build and runtime profile from the RTX 4090 fork. Relevant engine optimizations include:
The fork targets NVIDIA Ada sm_89, the RTX 4090's compute capability. It uses the groupwise-integer path rather than Blackwell-only NVFP4/W4A4 kernels.
The fork contains an sm_89-specific INT8 attention-prefill schedule using the Ada register budget and a retuned producer/consumer layout. The upstream project reports a 30% kernel gain on the tested 64K append shape and a 5-7% serving-prefill gain at 88K-128K. Those figures describe the fork's controlled benchmarks, not a guarantee for this conservative WSL profile.
KV state is allocated in pages rather than one monolithic per-session buffer. The page pool is shared across admitted slots, supports compatible-prefix reuse, and is planned before the server begins listening. An oversized explicit profile therefore fails during startup instead of failing partway through a request.
Prefix reuse is enabled by default because this launcher does not pass --no-prefix-reuse. A later request with a compatible retained prefix can avoid recomputing that prefix, which is valuable for repeated agent conversations and tool turns.
MTP proposes three future tokens and the target model verifies them. The fork reports 148.6 tokens/s at 81% draft acceptance on its code-generation benchmark versus 50.5 tokens/s with speculation disabled. That benchmark used CUDA Graphs, greedy decoding, INT8 KV, and a 1024-token prefill chunk; it should not be treated as a measured number for this installer's --no-cuda-graph WSL profile.
--lm-head-draft loads the smaller optimized proposal-head path selected for the published MTP measurements instead of the full proposal head. This reduces the speculative runtime's resident profile.
NInfer plans weights, sequence state, prefill scratch, speculative state, KV pages, and optional CUDA Graph allowance before serving. This profile supplies an explicit KV capacity instead of deriving capacity from free VRAM, so Windows-side VRAM drift does not silently change the available context from one launch to the next.
This setup is intentionally different from the fork's maximum-capacity examples.
The fork documents approximately 172,032 tokens as the measured text-only MTP3 ceiling with INT8 KV and 262,144 tokens with rk4v4-e8. Those profiles run close to the 24 GB limit. WSL shares the physical GPU with Windows desktop applications, so reported free VRAM can change between launches.
The installed 114,688-token profile sacrifices theoretical context for enough margin to start reliably. OMP is told the same contextWindow, preventing it from sending oversized prompts.
The E8 rk4v4-e8 mode fits the model's full native 262K context and the fork reports exact retrieval through 260K in its tests. It also reports a 5.7% decode cost and 1-2% prefill cost against INT8 at matched depth. This installer stays with INT8 because 114,688 tokens are sufficient for the intended coding workflow and the less compressed cache is the conservative quality choice.
CUDA Graphs reduce repeated decode-launch overhead and are enabled in the fork's headline benchmarks. They also require a graph family and reserved driver allowance for reachable batch shapes. --no-cuda-graph removes that source of startup sensitivity. This is a reliability tradeoff, not the peak-throughput configuration.
--max-concurrency 2 is measured upstream at roughly 1.5x aggregate decode throughput and costs about 390 MiB for the second lane under the measured graph-enabled profile. It does not make a single OMP request faster, and prompt prefill remains serialized, so this installer keeps one lane.
The launcher omits --vision. NInfer therefore does not load the vision tower, vision scratch workspace, or media request-transient allocation. OMP's model declaration similarly advertises text input only. This preserves VRAM for context and speculative decoding.
| Behavior | Result |
|---|---|
| Thinking | Enabled. The launcher does not pass --no-thinking. |
| Closed-turn reasoning retention | Disabled. The launcher does not pass --preserve-thinking, so prior hidden reasoning is not fed back into later prompts. |
| Compatible-prefix reuse | Enabled. The launcher does not pass --no-prefix-reuse. |
| Vision | Disabled because --vision is absent. |
| Device | CUDA device 0 because --device is absent. |
| Server default output limit | 8,192 tokens when the request omits a limit. OMP is configured with the same maxTokens: 8192. |
| Authentication | None. This is safe only because the listener is bound to 127.0.0.1. |
The installer writes this provider:
providers:
llama.cpp:
baseUrl: http://127.0.0.1:8080/v1
api: openai-completions
auth: none
models:
- id: qwen3.8-27b
name: Qwen3.8 27B (NInfer)
reasoning: true
input:
- text
tokenizer: qwen3
contextWindow: 114688
maxTokens: 8192OMP's default role is:
modelRoles:
default: llama.cpp/qwen3.8-27b:xhighImportant details:
baseUrlends in/v1, matching NInfer's OpenAI-compatible route prefix.api: openai-completionsuses OMP's completions-compatible adapter. It avoids Responses-only request fields that this NInfer build rejects.auth: nonematches the local unauthenticated server.reasoning: trueexposes reasoning controls to OMP.:xhighselects the model's extra-high reasoning effort for OMP's default role.contextWindowexactly matches--max-context.maxTokensmatches NInfer's default 8,192-token output ceiling.
~/projects/ninfer-4090/ NInfer source checkout
~/projects/ninfer-4090/build/apps/ compiled binaries
~/projects/ninfer-4090/models/ 16.96 GiB model
~/.local/bin/ninfer daily launcher/service controller
~/.local/bin/omp OMP Linux x64 binary
~/.local/state/ninfer/server.log server log
~/.local/state/ninfer/server.pid last background PID
~/.omp/agent/models.yml local provider declaration
~/.omp/agent/config.yml OMP default role
The installer is safe to re-run:
- already installed Debian packages are skipped;
- CUDA 13.1 is skipped when its
nvccis executable; - an existing NInfer Git checkout is reused rather than reset;
- an existing server binary is not rebuilt;
- the model is skipped only when its exact expected byte size is present;
- partial model downloads resume;
- an existing OMP binary is reused;
- the PATH marker is appended to
.bashrconce; models.ymlis replaced deterministically;config.ymlis preserved except for the NInfer default role andsetupVersion.
Fresh installs pin the NInfer commit, Hugging Face model revision, model size, CUDA toolkit line, and OMP version. This avoids an unreviewed upstream change silently producing a different machine.
Re-running the installer intentionally regenerates ~/.local/bin/ninfer and ~/.omp/agent/models.yml. Make permanent profile changes in this repository's install.sh, not only in those generated files.
Install or update the Windows NVIDIA driver, then restart WSL from PowerShell:
wsl --shutdownOpen Ubuntu again and verify:
nvidia-smiDo not install a separate Linux NVIDIA display driver inside WSL.
Inspect the server log:
tail -n 100 ~/.local/state/ninfer/server.logThen stop any failed process and retry:
ninfer stop
ninfer serveCommon causes are another process already using port 8080, insufficient free VRAM, or a damaged/incomplete model file.
Identify the listener:
ss -ltnp | grep ':8080'Stop the conflicting service, then run ninfer again. The OMP configuration and launcher both expect port 8080.
Verify:
grep -A2 '^modelRoles:' ~/.omp/agent/config.yml
cat ~/.omp/agent/models.ymlThe default must be llama.cpp/qwen3.8-27b:xhigh, and the provider URL must be http://127.0.0.1:8080/v1.
ninfer stopThe service is intentionally persistent across OMP sessions so reopening OMP does not reload 16.96 GiB of weights each time.