VLLM_APPLICATION_GUIDE

#1
by Jong-Seong - opened
Files changed (1) hide show
  1. VLLM_APPLICATION_GUIDE.md +0 -556
VLLM_APPLICATION_GUIDE.md DELETED
@@ -1,556 +0,0 @@
1
- # vLLM Migration Guide: Qwen3-Next-80B on NVIDIA Blackwell (GB10)
2
- ### *From Ollama Unloading Issues to vLLM Success*
3
-
4
- ## 📋 Overview
5
-
6
- This guide documents our journey deploying **Qwen3-Next-80B** on the **NVIDIA DGX Spark (GB10)** - from encountering critical model unloading issues with Ollama, through failed TensorRT attempts, to finally achieving stable deployment with vLLM.
7
-
8
- **The Problem**: On a single DGX Spark (GB10) system, Ollama would intermittently unload the 80B model, causing service disruptions and requiring constant monitoring. TensorRT-LLM attempts also failed due to compatibility issues with Qwen3-Next's MoE architecture.
9
-
10
- **The Solution**: vLLM with OpenAI-compatible API, providing permanent VRAM reservation, PagedAttention parallelism, and native Blackwell (sm_121) acceleration.
11
-
12
- ---
13
-
14
- ## 🚨 The Journey: From Ollama to vLLM
15
-
16
- ### Phase 1: The Ollama Unloading Problem
17
-
18
- **Initial Setup**: We deployed Qwen3-Next-80B using Ollama on DGX Spark (GB10) with 120GB VRAM.
19
-
20
- **The Issue**:
21
- - Model would unload unexpectedly during idle periods
22
- - Service would fail when requests arrived after model unload
23
- - Required constant "keep-alive" requests to prevent unloading
24
- - No reliable way to ensure model permanence in VRAM
25
-
26
- **Symptoms**:
27
- ```
28
- [Ollama] Model "nika" unloaded from memory
29
- [Service] Request failed: Model not loaded
30
- [Service] Attempting to reload model...
31
- [Service] Reload takes 30-60 seconds
32
- ```
33
-
34
- **Root Cause**: Ollama's memory management is designed for multi-model scenarios and doesn't guarantee permanent model retention, especially for large 80B models.
35
-
36
- ---
37
-
38
- ### Phase 2: TensorRT-LLM Attempt
39
-
40
- **Why We Tried TensorRT-LLM**:
41
- - Promised better performance on Blackwell architecture
42
- - Lower latency potential
43
- - Better memory efficiency
44
-
45
- **The Failure**:
46
- - Qwen3-Next's MoE (Mixture of Experts) architecture not fully supported
47
- - Shared expert layers caused loading failures
48
- - Weight key mapping issues (`model.` prefix mismatch)
49
- - Incomplete MoE routing implementation
50
-
51
- **Error Messages**:
52
- ```
53
- [TensorRT-LLM] Error: Shared expert layers not recognized
54
- [TensorRT-LLM] Error: Weight key mismatch: model.layers.0 vs layers.0
55
- [TensorRT-LLM] Error: MoE routing not implemented for Qwen3-Next
56
- ```
57
-
58
- **Decision**: Abandoned TensorRT-LLM due to architectural incompatibility with Qwen3-Next's MoE structure.
59
-
60
- ---
61
-
62
- ### Phase 3: vLLM Success
63
-
64
- **Why vLLM Worked**:
65
- - Native support for MoE architectures
66
- - Permanent VRAM reservation (model stays loaded)
67
- - OpenAI-compatible API (easy migration)
68
- - PagedAttention for high throughput
69
- - Active development and Qwen3-Next support
70
-
71
- **The Solution**:
72
- - vLLM server runs as a separate service
73
- - Model loaded once at server start, stays in VRAM permanently
74
- - Application connects via OpenAI-compatible API
75
- - No more unloading issues
76
-
77
- ---
78
-
79
- ## 🏗️ Qwen3-Next-80B MoE Architecture
80
-
81
- Understanding the architecture is crucial for deployment:
82
-
83
- ### Architecture Overview
84
-
85
- Qwen3-Next-80B uses a sophisticated **Mixture of Experts (MoE)** architecture:
86
-
87
- - **Total Parameters**: ~80B
88
- - **Active Parameters (A3B)**: Only ~3B parameters are active per token
89
- - **Shared Experts**: Hybrid approach with both "routed experts" and "shared experts"
90
- - **Benefits**: Fast inference for its size while maintaining global knowledge
91
-
92
- ### Why This Matters
93
-
94
- 1. **Memory Efficiency**: Only 3B active parameters per token, but full 80B model must stay in VRAM
95
- 2. **Shared Experts**: Maintain global knowledge across all tokens
96
- 3. **Weight Mapping**: Special handling required for shared expert layers
97
-
98
- ---
99
-
100
- ## 🛠️ Critical Technical Challenges & Solutions
101
-
102
- ### 1. The Weight Key Mismatch (The `model.` Prefix)
103
-
104
- **Problem**: Hugging Face checkpoints save weights with a `model.` prefix (e.g., `model.layers.10...`), while vLLM's internal `Qwen2/3` implementation expects keys to start directly with `layers.10...`.
105
-
106
- **Solution**: vLLM's weight loader handles this automatically, but you may need to ensure checkpoint format compatibility.
107
-
108
- **Example**:
109
- ```python
110
- # Weight key transformation needed
111
- # Before: "model.layers.10.attention.q_proj.weight"
112
- # After: "layers.10.attention.q_proj.weight"
113
- ```
114
-
115
- ### 2. Shared Expert Mapping
116
-
117
- **Problem**: Standard loaders may fail to recognize `shared_expert` layers.
118
-
119
- **Solution**: vLLM's `AutoWeightsLoader` correctly handles Qwen3-Next's MoE structure, including shared experts.
120
-
121
- **Example**:
122
- ```python
123
- # Shared expert layers in Qwen3-Next
124
- shared_expert_layers = [
125
- "mlp.shared_expert.gate_proj.weight",
126
- "mlp.shared_expert.up_proj.weight",
127
- "mlp.shared_expert.down_proj.weight"
128
- ]
129
- ```
130
-
131
- ### 3. Model Name Resolution
132
-
133
- **Problem**: Application uses friendly names like "nika", but vLLM needs actual model paths.
134
-
135
- **Solution**: Implement model name resolution in `VLLMService`.
136
-
137
- ---
138
-
139
- ## ⚡ Blackwell (GB10) Specific Optimizations
140
-
141
- The **DGX Spark (GB10)** is the first hardware to support **NVFP4**. To maximize performance for an 80B model:
142
-
143
- ### 1. Quantization Strategy: GPTQ-Int4A16
144
-
145
- Using **GPTQ Int4 weight-only quantization** allows the 80B model to fit comfortably in 120GB VRAM:
146
- - Model weights: ~40GB (quantized)
147
- - KV Cache: ~40GB (for high throughput)
148
- - System overhead: ~40GB
149
- - **Total**: ~120GB (perfect fit for GB10)
150
-
151
- ### 2. Critical Environment Flags
152
-
153
- For Blackwell (sm_121), these environment variables are **mandatory**:
154
-
155
- ```bash
156
- # Force the Triton compiler to find the correct Blackwell ptxas
157
- export TRITON_PTXAS_PATH=/usr/local/cuda-13.0/bin/ptxas
158
- export VLLM_USE_FLASHINFER_SAMPLER=1 # Enable Blackwell-optimized kernels
159
- export VLLM_USE_FLASHINFER_MOE=0 # Temporary workaround for MoE kernels if needed
160
- export TRITON_INTERPRET=0 # Disable interpreter mode (critical for performance)
161
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
162
- ```
163
-
164
- ---
165
-
166
- ## 💻 Implementation Guide
167
-
168
- ### 1. vLLM Server Setup
169
-
170
- **File**: `bin/qwen3_next_80b_gptq.sh`
171
-
172
- ```bash
173
- #!/bin/bash
174
- # Optimized for Qwen3-Next-80B-GPTQ-Int4 on DGX Spark (Blackwell)
175
-
176
- # 1. CUDA 13.0 Pathing (Must include nvcc for JIT)
177
- export CUDA_HOME=/usr/local/cuda-13.0
178
- export CUDA_PATH=/usr/local/cuda-13.0
179
- export PATH=$CUDA_HOME/bin:$PATH
180
- export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH
181
- export C_INCLUDE_PATH=$CUDA_HOME/include
182
- export CPLUS_INCLUDE_PATH=$CUDA_HOME/include
183
-
184
- # 2. Prevent Triton 'resize_()' errors by disabling Interpreter mode
185
- export TRITON_INTERPRET=0
186
- export TRITON_PTXAS_PATH=/usr/local/cuda-13.0/bin/ptxas
187
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
188
-
189
- # 3. Blackwell-specific optimizations
190
- export VLLM_USE_FLASHINFER_SAMPLER=1
191
- export VLLM_USE_FLASHINFER_MOE=0 # Disable if MoE kernels cause issues
192
- export TRITON_DISABLE_LINE_INFO=1
193
- export TORCH_COMPILE_DEBUG=0
194
- export TORCHDYNAMO_DISABLE=1
195
-
196
- # 4. Launch with Blackwell-specific tuning
197
- # --enforce-eager bypasses CUDA graph capture overhead on new sm_121 arch
198
- python3 -m vllm.entrypoints.openai.api_server \
199
- --model dazipe/Qwen3-Next-80B-A3B-Instruct-GPTQ-Int4A16 \
200
- --trust-remote-code \
201
- --tensor-parallel-size 1 \
202
- --gpu-memory-utilization 0.92 \
203
- --max-model-len 8192 \
204
- --enforce-eager \
205
- --disable-custom-all-reduce \
206
- --host 0.0.0.0 \
207
- --port 1107
208
- ```
209
-
210
- **Key Parameters**:
211
- - `--gpu-memory-utilization 0.92`: Leaves room for KV cache (critical for 80B model)
212
- - `--enforce-eager`: Better stability on Blackwell (bypasses CUDA graph)
213
- - `--max-model-len 8192`: Adjust based on your use case
214
-
215
- ### 2. Application Service Class
216
-
217
- **File**: `api/services/vllm_service.py`
218
-
219
- ```python
220
- import requests
221
- import time
222
- import threading
223
- from typing import List, Dict, Optional
224
-
225
- class VLLMService:
226
- """vLLM API Service (OpenAI Compatible)"""
227
-
228
- def __init__(self, host: str, model_name: str, embedding_model: str):
229
- self.host = host.rstrip('/')
230
- self.embedding_model = embedding_model
231
- self.api_base = f"{self.host}/v1"
232
- self._warmed_models = set()
233
- self._warm_up_lock = threading.Lock()
234
-
235
- # Convert model name to actual vLLM model path
236
- self.model_name = self._resolve_model_name(model_name)
237
-
238
- def _resolve_model_name(self, model_name: str) -> str:
239
- """
240
- Convert model name to actual vLLM model path
241
- - "nika" -> Actual model path available on vLLM server
242
- - If already a full path, return as-is
243
- """
244
- if "/" in model_name: # Already a full path
245
- return model_name
246
-
247
- try:
248
- response = requests.get(f"{self.api_base}/models", timeout=5)
249
- if response.status_code == 200:
250
- models_data = response.json()
251
- for model_info in models_data.get("data", []):
252
- model_id = model_info.get("id")
253
- # Special logic for NIKA (Qwen3-Next-80B)
254
- if "nika" in model_name.lower() and "qwen3" in model_id.lower():
255
- logger.info(f"✓ Resolved NIKA to Qwen3-Next-80B: {model_id}")
256
- return model_id
257
- if model_id and model_name.lower() in model_id.lower():
258
- return model_id
259
- except Exception as exc:
260
- logger.warning(f"Failed to resolve model name '{model_name}': {exc}")
261
-
262
- return model_name
263
-
264
- def check_connection(self) -> bool:
265
- """Check vLLM server connection"""
266
- try:
267
- response = requests.get(f"{self.api_base}/models", timeout=5)
268
- return response.status_code == 200
269
- except Exception:
270
- return False
271
-
272
- def generate_response(
273
- self,
274
- messages: List[Dict[str, str]],
275
- max_tokens: int = 512,
276
- temperature: float = 0.7,
277
- top_p: float = 0.9,
278
- top_k: Optional[int] = None,
279
- num_ctx: Optional[int] = None,
280
- stop: Optional[List[str]] = None,
281
- model_name: Optional[str] = None
282
- ) -> Dict[str, any]:
283
- """Generate response using vLLM OpenAI-compatible API"""
284
- request_id = f"req_{int(time.time() * 1000)}"
285
- # Resolve model name if provided
286
- if model_name:
287
- model_to_use = self._resolve_model_name(model_name)
288
- else:
289
- model_to_use = self.model_name
290
-
291
- logger.info(f"[{request_id}] Generating response via vLLM API... (model={model_to_use})")
292
-
293
- # Convert messages to OpenAI-compatible format
294
- openai_messages = []
295
- for msg in messages:
296
- role = msg.get("role", "user")
297
- content = msg.get("content", "")
298
- openai_messages.append({"role": role, "content": content})
299
-
300
- # vLLM OpenAI-compatible API request
301
- api_url = f"{self.api_base}/chat/completions"
302
-
303
- # Stop sequence settings
304
- default_stop = ["\n\nUser:", "Observation:", "<|endoftext|>", "<|eot_id|>", "\n\n\n"]
305
- stop_sequences = stop if stop is not None else default_stop
306
-
307
- payload = {
308
- "model": model_to_use,
309
- "messages": openai_messages,
310
- "max_tokens": max_tokens,
311
- "temperature": temperature,
312
- "top_p": top_p,
313
- "stop": stop_sequences,
314
- "stream": False
315
- }
316
-
317
- try:
318
- inference_start = time.time()
319
- response = requests.post(api_url, json=payload, timeout=300)
320
- inference_time = time.time() - inference_start
321
-
322
- if response.status_code == 200:
323
- result = response.json()
324
- choices = result.get("choices", [])
325
- if not choices:
326
- raise RuntimeError("vLLM API returned empty choices")
327
-
328
- choice = choices[0]
329
- response_text = choice.get("message", {}).get("content", "")
330
-
331
- # Token usage information
332
- usage = result.get("usage", {})
333
- prompt_tokens = usage.get("prompt_tokens", 0)
334
- completion_tokens = usage.get("completion_tokens", 0)
335
- total_tokens = usage.get("total_tokens", 0)
336
-
337
- # Check if response was truncated
338
- finish_reason = choice.get("finish_reason", "")
339
- was_truncated = finish_reason == "length" or completion_tokens >= (max_tokens * 0.95)
340
-
341
- logger.info(f"[{request_id}] [TIMING] vLLM API call: {inference_time*1000:.2f}ms")
342
- logger.info(f"[{request_id}] [STATS] Prompt: {prompt_tokens} tokens | Completion: {completion_tokens} tokens")
343
-
344
- return {
345
- "response_text": response_text,
346
- "was_truncated": was_truncated,
347
- "eval_count": completion_tokens,
348
- "max_tokens": max_tokens
349
- }
350
- else:
351
- error_msg = f"vLLM API error: {response.status_code} - {response.text}"
352
- logger.error(f"[{request_id}] {error_msg}")
353
- raise RuntimeError(error_msg)
354
- except requests.exceptions.RequestException as exc:
355
- error_msg = f"Request error: {exc}"
356
- logger.error(f"[{request_id}] {error_msg}")
357
- raise
358
- ```
359
-
360
- ### 3. API Endpoint Migration
361
-
362
- **Before (Ollama)**:
363
- ```python
364
- # Ollama endpoint
365
- response = requests.post(
366
- f"{ollama_host}/api/chat",
367
- json={
368
- "model": "nika",
369
- "messages": [...],
370
- "options": {
371
- "num_predict": 512,
372
- "temperature": 0.7,
373
- "keep_alive": -1 # Try to keep model loaded
374
- }
375
- }
376
- )
377
- ```
378
-
379
- **After (vLLM)**:
380
- ```python
381
- # vLLM OpenAI-compatible endpoint
382
- response = requests.post(
383
- f"{vllm_host}/v1/chat/completions",
384
- json={
385
- "model": "/root/.cache/huggingface/hub/models--dazipe--Qwen3-Next-80B...",
386
- "messages": [...],
387
- "max_tokens": 512,
388
- "temperature": 0.7,
389
- "top_p": 0.9
390
- # No keep_alive needed - model stays loaded permanently
391
- }
392
- )
393
- ```
394
-
395
- ---
396
-
397
- ## 🔍 Troubleshooting
398
-
399
- ### Q: Why does `nvidia-smi` show "Failed to initialize NVML"?
400
-
401
- **A**: This is often due to a "zombie" process holding the GB10 SoC. Run:
402
- ```bash
403
- sudo fuser -v /dev/nvidia*
404
- ```
405
- Kill the PIDs if found. If it persists, a cold reboot of the DGX Spark is required to re-initialize the Blackwell firmware.
406
-
407
- ### Q: vLLM is extremely slow (1 token/sec).
408
-
409
- **A**: You are likely in **Triton Interpreter Mode**. This happens if `nvcc` is not found during startup. Ensure `cuda-toolkit-13-0` is installed and the `PATH` is correctly set:
410
-
411
- ```bash
412
- which nvcc # Should output: /usr/local/cuda-13.0/bin/nvcc
413
- export TRITON_PTXAS_PATH=/usr/local/cuda-13.0/bin/ptxas
414
- ```
415
-
416
- ### Q: Model loading fails with "KeyError: 'model.layers.0'"
417
-
418
- **A**: This is the weight key mismatch issue. The checkpoint has `model.` prefix but vLLM expects keys without it. Solutions:
419
- 1. Use a checkpoint converter to strip the prefix
420
- 2. Patch vLLM's weight loader to handle both formats
421
- 3. Use a different checkpoint format (if available)
422
-
423
- ### Q: Shared expert layers not loading
424
-
425
- **A**: Ensure your vLLM version supports Qwen3-Next MoE architecture. You may need to:
426
- 1. Update vLLM to the latest version
427
- 2. Apply custom patches for shared expert handling
428
- 3. Use `--trust-remote-code` flag (already in script)
429
-
430
- ### Q: Model still unloads (like Ollama)
431
-
432
- **A**: This shouldn't happen with vLLM. If it does:
433
- 1. Check vLLM server logs for memory pressure
434
- 2. Verify `--gpu-memory-utilization` is not too high
435
- 3. Check for other processes using GPU memory
436
- 4. Ensure vLLM server process is not being killed
437
-
438
- ---
439
-
440
- ## 📈 Comparison: Ollama vs TensorRT vs vLLM
441
-
442
- | Feature | Ollama | TensorRT-LLM | vLLM |
443
- | --- | --- | --- | --- |
444
- | **80B Model Loading** | ❌ Intermittent Unloading | ❌ Failed (MoE issues) | ✅ **Permanent VRAM Reservation** |
445
- | **Throughput** | Sequential | N/A (failed) | ✅ **PagedAttention Parallelism** |
446
- | **Blackwell Support** | Generic | ✅ Optimized | ✅ **sm_121 Native Acceleration** |
447
- | **Architecture** | Dense Focused | ❌ MoE Incomplete | ✅ **MoE Optimized (Shared Experts)** |
448
- | **Quantization** | Limited Options | Good | ✅ **GPTQ-Int4A16 Optimized** |
449
- | **Memory Efficiency** | ~100GB+ VRAM | N/A | ✅ **~80GB VRAM (with quantization)** |
450
- | **API Compatibility** | Custom | Custom | ✅ **OpenAI Compatible** |
451
- | **Stability** | ❌ Unloading Issues | ❌ Failed | ✅ **Stable** |
452
-
453
- ---
454
-
455
- ## 📝 Key Learnings
456
-
457
- ### Why Ollama Failed
458
-
459
- 1. **Memory Management**: Ollama's design prioritizes flexibility over permanence
460
- 2. **Multi-Model Focus**: Optimized for switching between models, not keeping one loaded
461
- 3. **No Guarantee**: No reliable way to ensure 80B model stays in VRAM
462
-
463
- ### Why TensorRT-LLM Failed
464
-
465
- 1. **MoE Support**: Incomplete implementation for Qwen3-Next's MoE architecture
466
- 2. **Shared Experts**: Not properly handled in TensorRT-LLM's weight loader
467
- 3. **Architecture Mismatch**: Designed more for dense models
468
-
469
- ### Why vLLM Succeeded
470
-
471
- 1. **Permanent Loading**: Model loaded once, stays in VRAM permanently
472
- 2. **MoE Native**: Full support for Qwen3-Next's MoE architecture
473
- 3. **OpenAI API**: Easy migration path from existing code
474
- 4. **Active Development**: Regular updates and Qwen3-Next support
475
- 5. **Blackwell Optimized**: Native support for sm_121 architecture
476
-
477
- ---
478
-
479
- ## 🎯 Migration Checklist
480
-
481
- ### Pre-Migration
482
-
483
- - [ ] Identify all Ollama API calls in your codebase
484
- - [ ] Document current model loading/unloading behavior
485
- - [ ] Verify vLLM server can be installed and run
486
- - [ ] Test vLLM with a smaller model first
487
-
488
- ### Migration Steps
489
-
490
- 1. **Install vLLM**:
491
- ```bash
492
- pip install vllm
493
- ```
494
-
495
- 2. **Start vLLM Server**:
496
- ```bash
497
- bash bin/qwen3_next_80b_gptq.sh
498
- ```
499
-
500
- 3. **Update Service Class**:
501
- - Replace `OllamaService` with `VLLMService`
502
- - Update API endpoints from `/api/chat` to `/v1/chat/completions`
503
- - Update JSON payload format
504
-
505
- 4. **Update Configuration**:
506
- - Change `OLLAMA_HOST` to `VLLM_HOST`
507
- - Update model name resolution logic
508
-
509
- 5. **Test Thoroughly**:
510
- - Verify model stays loaded
511
- - Test response generation
512
- - Monitor memory usage
513
- - Check for any unloading issues
514
-
515
- ### Post-Migration
516
-
517
- - [ ] Monitor for 24-48 hours to ensure stability
518
- - [ ] Verify no model unloading occurs
519
- - [ ] Check performance metrics
520
- - [ ] Update documentation
521
-
522
- ---
523
-
524
- ## 📚 References
525
-
526
- ### vLLM Documentation
527
-
528
- - [vLLM Official Documentation](https://docs.vllm.ai/)
529
- - [OpenAI Compatible API](https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html)
530
-
531
- ### Qwen3-Next Model
532
-
533
- - [Qwen3-Next-80B Model Card](https://huggingface.co/dazipe/Qwen3-Next-80B-A3B-Instruct-GPTQ-Int4A16)
534
-
535
- ### Blackwell Architecture
536
-
537
- - [NVIDIA Blackwell Architecture](https://www.nvidia.com/en-us/data-center/blackwell/)
538
-
539
- ---
540
-
541
- ## 🎉 Success Metrics
542
-
543
- After migrating to vLLM:
544
-
545
- - ✅ **Zero model unloading incidents** (previously daily occurrences)
546
- - ✅ **Stable service** (no more "model not loaded" errors)
547
- - ✅ **Better throughput** (PagedAttention parallelism)
548
- - ✅ **Lower latency** (no model reload overhead)
549
- - ✅ **Simpler architecture** (no keep-alive logic needed)
550
-
551
- ---
552
-
553
- **Last Updated**: 2025-12-31
554
- **Author**: [Jong-Seong Kim (김종성)](https://huggingface.co/dazipe)
555
- **Project**: LANIKA / NIKA AI
556
- **Hardware**: NVIDIA DGX Spark (GB10) - Single System Deployment