|
|
преди 5 дни | |
|---|---|---|
| .gitignore | преди 5 дни | |
| README.md | преди 5 дни | |
| benchmark.sh | преди 5 дни | |
| docker-compose.yaml | преди 5 дни | |
| download-model.sh | преди 5 дни | |
| proxy-passthrough.py | преди 5 дни |
Self-contained vLLM version of the parent coding-stack-huihui-qwen3.8-27b project.
Instead of the Q8 GGUF it serves the pre-quantized INT4 checkpoint
shawnw3i/Huihui-Qwen3.8-27B-abliterated-AWQ-MTP (auto-round AWQ, group_size 64,
18.65 GiB on disk) as the model foundation, behind the same OpenAI-compatible
proxy that injects a SearXNG web_search tool.
This is the fast backend: vLLM's paged KV cache, chunked prefill and Marlin INT4 kernels are what make the 250k-token context practical on one 48 GB card. It is also the least flexible one — one sequence at a time, no vision, no FP8 on this GPU.
Deliberately not included: the llama.cpp and Ollama compose files, the LibreChat/MongoDB/Meilisearch stack, GGUF/mmproj handling, and the parent's shared container names/API key.
┌─────────────── web_search ──────────────► SearXNG :8889 (external)
│
Client ──► :11433 proxy (Flask) ──► :18080 vLLM (OpenAI API)
▲
└── :18080 is also published directly, bypassing the proxy
Containers: coding-stack-vllm (vllm/vllm-openai:nightly) and
coding-stack-proxy (python:3.11-slim). Port 11433 and the proxy container name
clash with the parent and Ollama stacks — stop those first if you switch back and forth.
| File | Purpose |
|---|---|
docker-compose.yaml |
vLLM server + web_search proxy (the whole stack) |
proxy-passthrough.py |
OpenAI facade on :11433; injects and executes web_search |
download-model.sh |
Pulls the AWQ checkpoint into the HF cache vLLM reads |
benchmark.sh |
TTFT + decode tok/s from a streaming request |
.gitignore |
Keeps caches and weights out of git |
There is no Modelfile and no import step: vLLM loads the HF checkpoint directly, and the
chat template comes from the checkpoint's chat_template.jinja.
--gpu-memory-utilization 0.93--kv-cache-dtype fp8
(or an FP8 checkpoint) on this card: it won't run.vllm/vllm-openai:nightly (~29 GB, 0.27.2rc1 at the time of writing,
2 months old in this host's cache). It is a moving tag — re-pull deliberately.host.docker.internal:8889 for web_searchThe checkpoint is already in this host's cache:
/storage-models/vllm/hub/models--shawnw3i--Huihui-Qwen3.8-27B-abliterated-AWQ-MTP
(7 shards + model_extra_tensors.safetensors, size-verified in the parent project).
download-model.sh is a no-op when it is there.
cd vllm
./download-model.sh # ~19 GB; no-op if the HF cache already has it
docker compose up -d # first start: load + compile + CUDA graph capture (minutes)
docker compose ps # wait for (healthy) — start_period is 900 s
./benchmark.sh # TTFT + tok/s
Cold start is slow by design: vLLM compiles the model's kernels (torch.compile,
Triton GDN kernels) and captures CUDA graphs before it reports healthy. Expect several
minutes on first boot; subsequent boots reuse the compile cache in the image's
/root/.cache only if you persist it (this stack does not — add a volume if you care).
# Direct to vLLM (OpenAI-compatible)
curl http://localhost:18080/v1/models
curl http://localhost:18080/v1/chat/completions -H "Authorization: Bearer x" \
-d '{"model":"foundation","messages":[{"role":"user","content":"Hello"}],"max_tokens":64}'
# Through the proxy (+ web_search injected)
curl http://localhost:11433/v1/chat/completions -H "Authorization: Bearer x" \
-d '{"model":"foundation","messages":[{"role":"user","content":"Hello"}],"max_tokens":64}'
# Streaming (proxy fast-path: SSE passed straight through)
curl -N http://localhost:11433/v1/chat/completions -H "Authorization: Bearer x" \
-d '{"model":"foundation","messages":[{"role":"user","content":"Hello"}],"stream":true,"max_tokens":64}'
curl http://localhost:18080/health
vLLM is started without --api-key, so any Authorization value works (or none).
Add --api-key and BACKEND_API_KEY to the proxy if the published port must be closed.
| Port | Service | Notes |
|---|---|---|
11433 |
proxy (Flask) | OpenAI /v1/*; injects web_search; everything else is transparent |
18080 |
vLLM | OpenAI /v1/*, /health, /metrics; no auth |
Model name: foundation (--served-model-name), regardless of the HF repo name.
docker-compose.yaml)| Flag | Value | Why |
|---|---|---|
--max-model-len |
253952 |
fits 18.65 GiB weights + ~15.5 GiB f16 KV on 48 GB; native max is 262144 |
--gpu-memory-utilization |
0.93 |
leaves ~3 GiB for the desktop/other processes |
--max-num-seqs |
1 |
one 250k sequence consumes the whole KV pool |
--max-num-batched-tokens |
16384 |
chunked prefill granularity for long prompts |
--enable-chunked-prefill / --enable-prefix-caching |
on | keeps a 250k-token agent prompt responsive and cached across turns |
--reasoning-parser |
qwen3 |
splits the reasoning trace out of content |
--tool-call-parser |
qwen3_coder |
required for tool calls (the proxy's web_search) |
--enable-auto-tool-choice |
on | without it vLLM rejects requests that carry tools |
--language-model-only |
on | skips the vision tower (see Limitations) |
--dtype |
float16 |
the checkpoint's dtype; bf16 would double nothing but adds no benefit here |
--trust-remote-code |
on | auto-round checkpoint ships custom config |
The VLLM_* environment variables in the compose file are the parent project's tuning
for this exact card (Marlin atomic add, FlashInfer sampler, graph estimation, spawn
workers); they are not required for correctness.
KV is f16 on the 16 full-attention layers only (the 48 GDN layers keep a small recurrent state), so 64 KiB/token:
| Context | KV (f16) | Weights | Approx. total |
|---|---|---|---|
| 32k | ~2.0 GiB | 18.65 GiB | ~22 GiB |
| 128k | ~8.2 GiB | 18.65 GiB | ~29 GiB |
| 253952 (default) | ~15.5 GiB | 18.65 GiB | ~37 GiB |
| 262144 (native max) | ~16 GiB | 18.65 GiB | ~38 GiB |
Lower --max-model-len (and drop VLLM_ALLOW_LONG_MAX_MODEL_LEN if you go below the
model's 262144) if you need to share the GPU. Going up is not possible without a
second card or a smaller/quantized KV cache — and FP8 KV needs CC ≥ 8.9.
--reasoning-parser qwen3 moves the trace out of content. The model keeps Qwen3.8's
default reasoning level, so it thinks by default and reasoning tokens count against
max_tokens — give it 2048–4096 for real questions. Verified on this build (vLLM
0.27.2rc1): the trace arrives in the message's reasoning field (streaming:
delta.reasoning), the answer in content, and the split is reported in
usage.completion_tokens_details.reasoning_tokens. (Older vLLM releases used
reasoning_content; the proxy re-streams either.)
Thinking controls pass through chat_template_kwargs (verified):
# no reasoning at all
curl localhost:18080/v1/chat/completions -H 'Content-Type: application/json' \
-d '{"model":"foundation","messages":[{"role":"user","content":"Hi"}],
"chat_template_kwargs":{"enable_thinking":false}}'
# shorter trace (named level from the Qwen3.8 template)
... "chat_template_kwargs":{"reasoning_effort":"low"}
With default settings a 300- and even a 1000-token reply came back as pure reasoning
(finish_reason: "length", empty content) — this checkpoint thinks a lot. Set a high
max_tokens or lower the effort.
The proxy does not disable thinking and does not touch the reasoning field — it passes
completions through unchanged.
web_search--enable-auto-tool-choice --tool-call-parser qwen3_coder is what makes the proxy's
injected web_search tool usable. Verified: a get_weather request came back as
tool_calls: [{"function":{"name":"get_weather","arguments":"{\"city\": \"Sofia\"}"}}]
with finish_reason: "tool_calls". Behaviour is the same as the other backends:
:11433: the proxy runs the search loop server-side
(MAX_SEARCH_ITER, default 10) against SearXNG and returns only the final answer. The
client's own tool calls are returned untouched, unless the model mixes them into a search
turn (then they are stubbed — inherited edge case).web_search, the client gets
the tool_call and must execute it.SEARXNG_URL / SEARXNG_RESULTS tune the backend (defaults
http://host.docker.internal:8889, 5). extra_hosts is what resolves that hostname.Exceeded max search iterations (2 searches, 2 empty) — SearXNG returned no usable results.
Lower MAX_SEARCH_ITER (env on the proxy) if you prefer to fail fast.base_url = http://localhost:11433/v1 # or http://localhost:18080/v1 to skip web_search
model = foundation
api_key = anything
Use the proxy for coding agents so web_search works, and set max_tokens high enough
for the reasoning trace. Direct vLLM (:18080) is also OpenAI-compatible, including
native json_schema structured output — the vLLM build does not need the grammar
stripping that llama.cpp did (dropped from this proxy).
--language-model-only drops the visual tower and vLLM logs
running in text-only mode. Remove the flag and lower --max-model-len if you need
images; the processor configs are in the checkpoint.--max-num-seqs 1: a second concurrent request queues.--api-key omitted).vllm/vllm-openai:nightly moves; v0.18.0 (also cached on this host)
predates qwen3_5 support. Pin the digest if you need reproducibility.mtp, lm_head, the visual merger
and some linear_attn.in_proj_a/b tensors in fp16, but the bulk of the weights are
4-bit; the llama.cpp/Ollama stacks with UD-DW-Q8_K_L are the near-lossless option.Measured with ./benchmark.sh on this RTX A6000 with the GPU otherwise idle (only ~0.3 GiB
of desktop usage), default settings, 253952 context:
| Metric | Value |
|---|---|
| decode (300 tokens) | 38.5 tok/s |
| decode (1000 tokens) | 38.3 tok/s |
| TTFT (92-token prompt, warm) | 0.10 s |
| prefill (9,062-token prompt) | ~1.8k tok/s (5.9 s wall, including 32 decoded tokens) |
| cold start → healthy | ~4 min (06:52:28 → 06:56:41, kernels compiled + graphs captured) |
| VRAM at steady state | 42.9 GiB: 17.7 GiB weights/non-torch, 2.74 GiB peak activations, 0.04 GiB CUDA graphs, 23.6 GiB KV pool |
Comparison: llama.cpp with the Q8 GGUF measures 9.95 tok/s on the same card, so vLLM INT4 is ~3.9× the decode rate (the parent project hoped for 2–3×). The trade is fidelity: 4-bit AWQ vs near-lossless Q8.
The KV pool is 379,388 tokens (65 KiB/token f16) — --max-model-len 253952 only uses
~67% of it, so the context could be raised to the native 262144; the rest is headroom for
one long prompt, not a second sequence (--max-num-seqs 1).
Two implementation details that affect speed on this card (from the vLLM log):
Using MarlinLinearKernel for AutoAWQMarlinLinearMethod — the INT4 path is Marlin.Falling back to the Triton GDN decode path — the fused CUDA GDN kernel requires a
BF16 model with K=V=128; this FP16 checkpoint uses the Triton kernel instead.| Problem | Fix |
|---|---|
ValueError: No available memory for the cache blocks / OOM at startup |
lower --max-model-len (e.g. 131072) or --gpu-memory-utilization; free the GPU first (nvidia-smi) |
Container stuck in health: starting |
it is compiling/capturing graphs — watch docker compose logs -f vllm; allow up to 15 min on first boot |
model ... not found / 404 |
./download-model.sh; confirm HF_HOME=/models and the cache path under /storage-models/vllm/hub |
| AWQ kernel / quant errors | try --quantization compressed-tensors, or add --enforce-eager (slower but skips CUDA graphs) |
| Requests queue behind each other | expected with --max-num-seqs 1; raise it only if you lower --max-model-len |
| Tool calls rejected / not parsed | the parsers are mandatory: --enable-auto-tool-choice --tool-call-parser qwen3_coder |
reasoning empty |
check --reasoning-parser qwen3 is present; it is what extracts the trace |
Empty content, finish_reason: "length" |
thinking ate the budget — raise max_tokens or pass chat_template_kwargs: {"enable_thinking": false} |
Exceeded max search iterations (N searches, M empty) |
SearXNG returned nothing; check its engines, or lower MAX_SEARCH_ITER on the proxy |
| Proxy returns 502 | docker compose logs vllm — usually a failed load; also confirm BACKEND_URL=http://vllm:8000 |
web_search → Search failed |
SearXNG on host.docker.internal:8889 is down (external to this stack) |
| Startup warning about multimodal limits | harmless: it is the text-only mode you asked for |
docker compose down # keeps the HF cache and the image
# nuclear: delete the checkpoint cache (19 GB) and re-download with ./download-model.sh
sudo rm -rf /storage-models/vllm/hub/models--shawnw3i--Huihui-Qwen3.8-27B-abliterated-AWQ-MTP