Skip to content

Commit 068bf65

Browse files
OriNachumclaude
andcommitted
docs: add per-model runtime docs; benchmark Qwen3-32B, evaluate Qwen3.6-27B
Add docs/<model>.md per runtime model: - qwen3-32b-nvfp4.md: live test on DGX Spark (GB10) with the nv26.04 vLLM image — ~9.7 tok/s decode (batch=1), ~2,800 tok/s prefill, ~72 GB reserved at gpu-memory-utilization=0.6, correctness verified. - qwen3.6-27b-nvfp4.md: candidate mmangkad/Qwen3.6-27B-NVFP4. Its Qwen3_5ForConditionalGeneration arch is registered in the current vLLM image, so the same compose can serve it; live load-test tracked by #6. Fix the docker-compose --reasoning-parser comment: on the nv26.04 build the <think> trace returns in the `reasoning` field, not `reasoning_content`. Link both docs from README; bump version to 0.3.0. Refs #6 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 22c2f01 commit 068bf65

6 files changed

Lines changed: 199 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ All notable changes to this project are documented here. The format is based on
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
55
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.3.0] - 2026-05-27
8+
9+
### Added
10+
11+
- `docs/qwen3-32b-nvfp4.md`: per-model doc for the current runtime model, with a
12+
live test on DGX Spark (GB10) — `nvcr.io/nvidia/vllm:26.04-py3` (engine
13+
`0.19.0+...nv26.04`), ~9.7 tok/s decode (batch=1), ~2,800 tok/s prefill, ~72 GB
14+
reserved at `gpu-memory-utilization=0.6`, correctness verified.
15+
- `docs/qwen3.6-27b-nvfp4.md`: per-model doc for candidate
16+
`mmangkad/Qwen3.6-27B-NVFP4`. Its `Qwen3_5ForConditionalGeneration` arch is
17+
registered in the current vLLM image (so the same compose can serve it); live
18+
load-test/benchmark tracked by issue #6.
19+
- README "Per-model notes" linking both docs.
20+
21+
### Fixed
22+
23+
- `docker-compose.yml`: corrected the `--reasoning-parser=qwen3` comment — on the
24+
nv26.04 build the `<think>` trace is returned in the `reasoning` field, not
25+
`reasoning_content`.
26+
727
## [0.2.0] - 2026-05-27
828

929
### Added

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,14 @@ in-container alongside `HF_TOKEN` and the mounted cache. Add it back only for a
6363
model whose repo ships custom modeling code. If vLLM rejects the `nvidia/`
6464
ModelOpt checkpoint, set `VLLM_MODEL` to the vLLM-native `RedHatAI/Qwen3-32B-NVFP4`
6565
and drop `--quantization` from the compose `command`.
66+
67+
### Per-model notes
68+
69+
Each runtime model has a doc under `docs/` recording how to run it, live test
70+
results, and caveats:
71+
72+
- [`docs/qwen3-32b-nvfp4.md`](docs/qwen3-32b-nvfp4.md) — the **current** runtime
73+
model (`nvidia/Qwen3-32B-NVFP4`), benchmarked on DGX Spark.
74+
- [`docs/qwen3.6-27b-nvfp4.md`](docs/qwen3.6-27b-nvfp4.md) — a **candidate**
75+
(`mmangkad/Qwen3.6-27B-NVFP4`); architecture is supported by the current vLLM
76+
image, live load-test pending.

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ services:
5252
- --kv-cache-dtype=fp8
5353
- --max-model-len=${VLLM_MAX_MODEL_LEN:-32768}
5454
- --gpu-memory-utilization=${VLLM_GPU_MEM_UTIL:-0.6}
55-
- --reasoning-parser=qwen3 # expose the <think> trace as reasoning_content (drop on older vLLM)
55+
- --reasoning-parser=qwen3 # expose the <think> trace (field is `reasoning` on the nv26.04 build, `reasoning_content` on older vLLM; drop the flag on builds without the parser)
5656
- --enable-prefix-caching
5757
# NOTE: --trust-remote-code is intentionally omitted. Qwen3-32B-NVFP4 loads
5858
# without it; add it back only for a model whose repo ships custom modeling

docs/qwen3-32b-nvfp4.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Runtime model: `nvidia/Qwen3-32B-NVFP4`
2+
3+
lepenseur's **current runtime model**, served by local vLLM over the `acp`
4+
backend. Declared in `culture.yaml` as `vllm-local/nvidia/Qwen3-32B-NVFP4` and
5+
stood up by `docker-compose.yml`.
6+
7+
## What it is
8+
9+
- 32B **dense** reasoning model, quantized to **NVFP4** (NVIDIA ModelOpt FP4).
10+
- Native **32K** context (`max_model_len 32768`), extendable to ~131K via YaRN
11+
rope scaling.
12+
- **Thinking mode:** emits a `<think>` reasoning trace before its answer — which
13+
is why it suits a deep thinker.
14+
- Repo is **public** (no `HF_TOKEN` needed). ~20 GB on disk.
15+
16+
## How to run
17+
18+
```bash
19+
cp .env.example .env # optional; set HF_TOKEN only for gated repos
20+
docker login nvcr.io # NGC API key, to pull the vLLM image
21+
docker compose up -d
22+
docker compose logs -f vllm # first run downloads ~20 GB of weights
23+
```
24+
25+
Verify:
26+
27+
```bash
28+
curl -fsS http://localhost:8000/health
29+
curl -s http://localhost:8000/v1/models # lists nvidia/Qwen3-32B-NVFP4
30+
```
31+
32+
Relevant compose flags: `--quantization=modelopt_fp4`, `--kv-cache-dtype=fp8`,
33+
`--reasoning-parser=qwen3`, `--enable-prefix-caching`,
34+
`--gpu-memory-utilization=0.6`. Tunables in `.env` (`VLLM_GPU_MEM_UTIL`,
35+
`VLLM_MAX_MODEL_LEN`, `HF_CACHE`, …).
36+
37+
## Reading the reasoning trace
38+
39+
> **Field name caveat.** On the `nvcr.io/nvidia/vllm:26.04-py3` build (engine
40+
> `0.19.0+...nv26.04`), `--reasoning-parser=qwen3` returns the `<think>` trace in
41+
> the message field **`reasoning`**, *not* `reasoning_content`. Clients (and any
42+
> acp `vllm-local` wiring) should read `message.reasoning`. Older vLLM builds use
43+
> `reasoning_content` — check the field name against your image.
44+
45+
A reasoning model spends most of its tokens thinking, so give it room: a tight
46+
`max_tokens` can be consumed entirely inside the trace, leaving `content` empty
47+
with `finish_reason: length`.
48+
49+
## Live test — 2026-05-27, DGX Spark (GB10)
50+
51+
Verified end-to-end on the GB10 (121 GB unified memory). Served on port `8001`
52+
during the test (compose default is `8000`) to avoid a co-resident service.
53+
54+
| Property | Value |
55+
|---|---|
56+
| Image / engine | `nvcr.io/nvidia/vllm:26.04-py3` / vLLM `0.19.0+...nv26.04` |
57+
| Weights on disk | ~20 GB |
58+
| GPU memory reserved | ~72 GB (`gpu-memory-utilization=0.6`; 74,136 MiB observed) |
59+
| Health / models | `/health` 200; `/v1/models` lists the model, `max_model_len 32768` |
60+
| Correctness | "14:45→17:10 = 145 min" ✅; "17 × 23 = 391" ✅, both with full reasoning trace |
61+
| **Decode throughput** | **~9.7 tok/s** (batch=1, greedy, 512 tokens forced; identical across 2 runs) |
62+
| **Prefill** | ~2,014 prompt tokens in ~0.7 s (~2,800 tok/s); 2.37 s incl. 16 decode tokens |
63+
64+
Decode at ~10 tok/s reflects a 32B dense model on the GB10's low-power unified
65+
memory — adequate for a deliberate, write-by-thinking agent, not for
66+
high-throughput serving.
67+
68+
### Known noise
69+
70+
`env_file: .env` passes the compose-interpolation vars (`VLLM_MODEL`,
71+
`VLLM_PORT`, …) into the container, so vLLM logs harmless
72+
`Unknown vLLM environment variable detected: VLLM_*` warnings at startup. They do
73+
not affect serving.
74+
75+
## Fallback
76+
77+
If a vLLM build rejects the `nvidia/` ModelOpt checkpoint, set
78+
`VLLM_MODEL=RedHatAI/Qwen3-32B-NVFP4` and drop `--quantization` from the compose
79+
`command` (the RedHatAI checkpoint is vLLM-native).

docs/qwen3.6-27b-nvfp4.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Candidate model: `mmangkad/Qwen3.6-27B-NVFP4`
2+
3+
A candidate alternative runtime model. **Architecturally supported** by the
4+
vLLM image lepenseur already runs, but **not yet load-tested live** — see the
5+
benchmark plan below. Tracked by [issue #6](https://github.com/agentculture/lepenseur/issues/6).
6+
7+
Source: <https://huggingface.co/mmangkad/Qwen3.6-27B-NVFP4> — public, Apache-2.0.
8+
9+
## What it is
10+
11+
- NVFP4 (NVIDIA ModelOpt) quantization of **`Qwen/Qwen3.6-27B`**.
12+
- `config.json`: `architectures: ["Qwen3_5ForConditionalGeneration"]`,
13+
`model_type: qwen3_5`, 64 layers, `hidden_size 5120`,
14+
`max_position_embeddings 262144` (**256K** context), multimodal RoPE
15+
(`mrope_interleaved`, `mrope_section`).
16+
- ~20B effective params after compression; ~20 GB on disk (BF16 / F8_E4M3 / U8
17+
tensors). ModelOpt producer `0.42.0rc1.dev107` (a dev/rc build).
18+
19+
## Is it supported here? — Yes (architecture), pending live load
20+
21+
The deciding check: query the **running** vLLM engine's model registry rather
22+
than guess.
23+
24+
```text
25+
$ docker exec lepenseur-vllm python3 -c \
26+
"from vllm.model_executor.models.registry import ModelRegistry; \
27+
print('Qwen3_5ForConditionalGeneration' in ModelRegistry.get_supported_archs())"
28+
True
29+
```
30+
31+
The `nvcr.io/nvidia/vllm:26.04-py3` image (engine `0.19.0+...nv26.04`) registers
32+
`Qwen3_5ForConditionalGeneration` (plus `Qwen3_5MoeForConditionalGeneration` and
33+
`Qwen3_5MTP`) — the exact architecture this checkpoint declares. The quant flag
34+
(`--quantization=modelopt_fp4`) and `--reasoning-parser=qwen3` are the same ones
35+
already working for the 32B. So the same compose can serve it.
36+
37+
"Registered" means vLLM can instantiate the model class; it does not prove the
38+
weights load and serve cleanly. That requires the live load-test below.
39+
40+
## How to run (same compose, model override)
41+
42+
```bash
43+
# in .env
44+
VLLM_MODEL=mmangkad/Qwen3.6-27B-NVFP4
45+
VLLM_SERVED_NAME=mmangkad/Qwen3.6-27B-NVFP4 # must match culture.yaml's vllm-local/<name>
46+
# keep --quantization=modelopt_fp4 and --reasoning-parser=qwen3 (already in compose)
47+
docker compose up -d
48+
```
49+
50+
Memory note: at 256K context the KV cache is large. Keep
51+
`VLLM_MAX_MODEL_LEN=32768` (or similar) for a first load; only raise it with
52+
headroom to spare. The GB10 has 121 GB unified memory total.
53+
54+
## Caveats to validate during the load-test
55+
56+
1. **SGLang is the blessed runtime.** The model card recommends `sglang serve`
57+
(with `--tool-call-parser qwen3_coder`), not vLLM. vLLM support is present in
58+
the registry but is not the card's documented path.
59+
2. **`ForConditionalGeneration` + multimodal RoPE.** The arch and `mrope` config
60+
suggest a vision/multimodal lineage; text-only chat should still serve, but
61+
confirm vLLM does not demand an image/processor path at load.
62+
3. **ModelOpt dev/rc producer** (`0.42.0rc1.dev107`) — verify the quant config
63+
parses under this vLLM build.
64+
65+
## Benchmark plan (to be filled when load-tested)
66+
67+
Run the same methodology used for the 32B (see
68+
[`qwen3-32b-nvfp4.md`](qwen3-32b-nvfp4.md)) so the two are comparable:
69+
70+
- Health + `/v1/models` reachable.
71+
- Correctness on the same two probes (train-times, `17 × 23`), confirming the
72+
`reasoning` field populates.
73+
- Decode throughput: 512 tokens forced (`ignore_eos`), batch=1, greedy.
74+
- Prefill: ~2K-token prompt, 16-token gen.
75+
- Record image/engine version, weights-on-disk, and GPU memory reserved.
76+
77+
| Property | Value |
78+
|---|---|
79+
| Decode throughput | _TBD_ |
80+
| Prefill | _TBD_ |
81+
| GPU memory reserved | _TBD_ |
82+
| Correctness | _TBD_ |
83+
84+
### For comparison — 32B baseline (2026-05-27, GB10)
85+
86+
~9.7 tok/s decode (batch=1), ~2,800 tok/s prefill, ~72 GB reserved at
87+
`gpu-memory-utilization=0.6`.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "lepenseur"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
description = "lepenseur — the local thinking agent of the Culture mesh."
55
readme = "README.md"
66
license = "MIT"

0 commit comments

Comments
 (0)