Files
variet_llm/docs/v3_balanced_retuning_log.md
Variet-Worker 0dee779a73 refactor(phase-01): v3 retune fast & balanced roles
fast (Gemma 4 26B-A4B):
- Enable mmproj GPU loading (vision ~1s, 12x faster than CPU)
- KV f16 → q8_0 (save ~2.5 GB VRAM for mmproj)
- Tensor split 0.5,0.5 → 0.43,0.57 (13/17 layers)
- Remove --mlock/--poll/--prio/-t/-tb (no measurable impact)
- measured_tps 74.65 → 71.89 (trade 3.7% speed for vision)

balanced (Qwen 3.5 35B-A3B):
- Tensor split 0.5,0.5 → 0.48,0.52 (enables pipeline parallelism)
- Ubatch 128 → 256 (prefill +78%: 649 → 1,157 t/s)
- mmproj + --no-mmproj-offload (CPU vision, VRAM headroom)
- Remove useless flags same as fast
- measured_tps 61.62 → 64.16 (+4.1%)

Other:
- Document full retuning in docs/v3_{fast,balanced}_retuning_log.md
- Session report at .planning/reports/20260411-session-report.md
- Add bench utilities: bench_short/bench_long/test_ts_ratios
- Speculative decoding (E2B draft) experimented but rejected
  (+14% gen vs -31% cold start + tokenizer mismatch + mmproj conflict)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 14:55:27 +09:00

273 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v3 — Balanced Role Retuning Log (Qwen 3.5 35B-A3B)
**Date:** 2026-04-11
**Target role:** `balanced` (Qwen3.5-35B-A3B Q4_K_M)
**Goal:** 기존 `measured_tps: 61.62` 기준을 재검증하고, 진짜 최적 구성을 실측 기반으로 확정
**Result:** 최종 TPS **64.16 t/s** (짧은 프롬프트) / **62.00 t/s** (3,100 토큰 긴 프롬프트)
**Status:** ✅ 확정
---
## 1. 재튜닝 동기
Phase 01 종료 후 `engine_models.json``balanced` 설정이 여러 이유로 일관되지 않게 수정되어 있었음:
- `--mmproj` 추가 (비전 지원용, 다른 작업자가 넣음)
- `--mlock --poll 50 --prio 3` 등 Phase 01 최종본과 불일치
- `-ts 0.5,0.5` (이중 GPU 분할) 상태에서 compute buffer OOM 발생
- 실측 속도가 레퍼런스(61.62) 대비 33~42 t/s 수준으로 떨어짐
재튜닝을 통해 **원인 규명** + **안정 + 최적 설정 확정**이 목표.
---
## 2. 하드웨어 진단 (핵심 발견)
| GPU | 모델 | 최대 PCIe | 현재 |
|-----|------|---------|------|
| 0 | RTX 3060 12GB | Gen3 x16 | **Gen3 x4** (슬롯 4레인) |
| 1 | RTX 3060 12GB | Gen4 x16 | **Gen4 x16** |
**핵심: GPU 0은 PCIe 3.0 × 4 슬롯에 있음 (3.94 GB/s).** GPU 1은 PCIe 4.0 × 16 (31.5 GB/s). GPU 0이 GPU 1의 1/8 대역폭.
이 비대칭이 모든 하이브리드/멀티-GPU 성능 문제의 근본 원인.
---
## 3. Qwen3.5-35B-A3B 아키텍처 실측 데이터
llama-server 로드 로그 기준:
```
architecture = qwen35moe
file size = 20.49 GiB (Q4_K_M, 5.08 BPW)
n_params = 34.66 B
n_layer = 40
n_head_kv = 2
n_embd_head_k = 256
n_embd_head_v = 256
n_expert = 256 (activated: 8)
full_attention_interval = 4
```
**중요:** 40 레이어 중 **full attention은 10개만** (매 4번째). 나머지 30개는 **Gated Delta Net (SSM/Mamba-like) 레이어**로 recurrent state 사용. KV 캐시는 10 레이어에만 발생.
### KV 캐시 실측
| 컨텍스트 | KV 캐시 (q4_0) |
|---------|---------------|
| 128K | 720 MiB |
| 256K | 1,440 MiB |
(초기 추정 5GB는 오류였음 — 40레이어 전부 attention이라고 오해)
---
## 4. `-ts` (Tensor Split) 비율 스윕 결과
자동화 스크립트(`scripts/test_ts_ratios.py`)로 여러 비율 테스트:
| ratio | status | PP | c0 model | c1 model | c0 compute | c1 compute |
|-------|--------|-----|---------|---------|-----|-----|
| 0.5,0.5 | ready | **Fallback** | 10,540 | 9,931 | 203 | 123 |
| **0.48,0.52** | ready | ✅ **ON** | 10,036 | 10,434 | 600 | 384 |
| 0.47,0.53 | ready | ✅ ON | 10,036 | 10,434 | 600 | 384 |
| 0.45,0.55 | error | — | — | — | — | — |
| 0.43,0.57 | error | — | — | — | — | — |
| 0.40,0.60 | error | — | — | — | — | — |
**해석:**
- 40 레이어 기준 llama.cpp가 ratio를 정수 레이어로 반올림: 0.48 & 0.47 둘 다 19/21 분할이라 동일한 메모리 배치
- 0.5,0.5 (20/20)에서는 CUDA0 compute buffer가 PP 모드 요구치(600 MiB)를 수용 못해 자동 Fallback
- 0.45,0.55 이상은 CUDA1이 22+ 레이어 적재로 OOM
- **결론: PP on 유일 비율은 0.48,0.52 (또는 동등한 0.47,0.53)**
---
## 5. `-ub` (Ubatch) 스윕 결과 — 핵심 발견
짧은 프롬프트만 테스트해서 처음에 `-ub` 효과를 놓쳤음. 긴 프롬프트(3,100 토큰)로 재측정:
| 설정 | PP | Prompt t/s | Gen t/s | 3,100 토큰 prefill | GPU0 여유 |
|------|-----|-----------|---------|-----------------|---------|
| ub 128 | ✅ ON | 649 | 62.01 | 4.85s | 216 MiB |
| **ub 256** | ❌ OFF | **1,157** | **62.00** | **2.68s** | **411 MiB** |
| ub 384 b 768 | ❌ OFF | 1,275 | 61.61 | 2.43s | 133 MiB |
**핵심 인사이트:**
1. **`-np 1` 단일 사용자 환경에서 PP는 실질 이득 없음** — Pipeline Parallelism은 다중 요청 배칭에 의미가 있음. 단일 시퀀스면 overlap 할 대상이 없음.
2. **PP off가 오히려 유리** — compute buffer 작아져서 `-ub` 더 올릴 수 있음 → prefill 속도 대폭 향상
3. **`-ub` 수익률 체감:**
- 128 → 256: **+78%** (649 → 1,157 t/s)
- 256 → 384: +10.2% (1,157 → 1,275 t/s)
- 안정성 대비 256이 스윗 스팟
4. **Gen 속도는 `-ub`와 무관** — 모두 62 t/s. Gen은 KV 캐시 크기 + PCIe x4 병목이 결정.
---
## 6. mmproj 처리 결정
Qwen3.5-35B-A3B는 natively 멀티모달이라 mmproj 필요. 하지만 GPU에 올리면:
```
VRAM 수지 (256K, -ts 0.48,0.52):
모델 가중치: 10,036 (GPU0) + 10,434 (GPU1)
KV 캐시: 720 + 720 = 1,440 MiB
Compute: ~600 + ~384 MiB
mmproj: 858 MiB ← 추가 부담 → OOM
```
**해법: `--no-mmproj-offload` 추가** → mmproj를 CPU RAM에 유지.
| 항목 | GPU 오프로드 | **CPU 오프로드** |
|------|------------|------------|
| VRAM 절약 | — | 858 MiB |
| 텍스트 추론 | 동일 | **동일 (손실 0)** |
| 이미지 인코딩 | GPU 빠름 | CPU 6.4초 (640×640 기준) |
**Hermes Agent 사용 패턴** = 95% 텍스트, 가끔 스크린샷 → **CPU 오프로드가 확실히 유리**.
### 이미지 토큰 계산식
```
patch_size = 16
n_merge = 2
→ tokens = (width/32) × (height/32)
```
| 해상도 | 토큰 |
|--------|------|
| 640×640 | 400 |
| 768×768 | 576 |
| 1024×1024 | 1,024 (권장 최소) |
| 2048×2048 | 4,096 (최대) |
---
## 7. 제거된 옵션 (실측 영향 없음 확인)
| 옵션 | 제거 이유 | Δ TPS |
|------|---------|-------|
| ~~`--mlock`~~ | 전용 추론기. 시스템 RAM 여유. mmap 페이지 잠금 불필요 | 0.04 |
| ~~`--poll 50`~~ | GPU polling. `-np 1` 환경에선 효과 없음 | 0.00 |
| ~~`--prio 3`~~ | 프로세스 우선순위. 전용기라 경쟁 없음 | 0.00 |
제거 후 속도: 64.16 t/s (유지)
---
## 8. 최종 확정 옵션
```json
{
"balanced": {
"display_name": "Qwen 3.5 35B (Balanced)",
"model_path": "models/Qwen3.5-35B-A3B-Q4_K_M.gguf",
"measured_tps": 64.16,
"args": [
"--mmproj", "models/mmproj-F16.gguf",
"--no-mmproj-offload",
"-ngl", "999",
"-c", "262144",
"-np", "1",
"-fa", "on",
"--cache-type-k", "q4_0",
"--cache-type-v", "q4_0",
"-ub", "256",
"-b", "512",
"-t", "6",
"-tb", "6",
"-ts", "0.48,0.52"
]
}
}
```
---
## 9. 최종 실측 성능
### 텍스트 추론
| 시나리오 | Prompt t/s | Gen t/s | VRAM 여유 |
|---------|-----------|---------|---------|
| 짧은 프롬프트 (170 tok) | — | **64.16** | GPU0 411 / GPU1 539 MiB |
| 긴 프롬프트 (3,100 tok) | **1,157** | **62.00** | 동일 |
### 비전 추론 (mmproj CPU)
| 단계 | 속도 / 시간 |
|------|------------|
| 이미지 인코딩 (CPU, 640×640) | 5.3초 (encode) + 1.0초 (decode) = **6.4초** |
| 이미지 이후 생성 | 62 t/s |
### VRAM 최종
```
GPU 0 11,900 MiB (여유 216 MiB) Gen3 x4 [PCIe 병목]
GPU 1 11,710 MiB (여유 401 MiB) Gen4 x16
합계 23,610 MiB 중 사용 │ 966 MiB 여유
```
### CPU RAM
```
llama-server Working Set: ~23 GB
├── mmap 모델 (lazy) 20.5 GB
├── mmproj (CPU 할당) 0.86 GB
├── CUDA_Host compute buffer 0.39 GB
├── CPU compute buffer 0.25 GB
└── 기타 ~0.08 GB
```
---
## 10. 알려진 구조적 제약
1. **GPU 0 PCIe 3.0 x4 슬롯 병목** — Gen 속도 62 t/s 상한의 주원인. 물리적 한계라 소프트웨어로 해결 불가.
2. **Pipeline Parallelism 자동 Fallback** — compute buffer가 `-ub 256` 시 CUDA0 한계 초과. 다만 `-np 1` 환경에선 실질 손실 없음.
3. **mmproj CPU 상주** — 이미지 인코딩 시 GPU 대비 ~3-5배 느림. 사용 빈도가 낮아 허용.
### 향후 개선 여지
- GPU 0을 PCIe 4.0 x16 슬롯으로 물리 이전 시 Gen 속도 추가 이득 기대 (~70+ t/s 가능성)
- 비전 사용이 잦아지면 `--no-mmproj-offload` 재고 필요
---
## 11. 레퍼런스 대비
```
Phase 01 측정치: 61.62 t/s
v3 확정치: 64.16 t/s (+4.1%)
```
Phase 01은 **단일 GPU 환경**에서 튜닝되었음 (`found 1 CUDA devices` 로그 확인). 현재는 **듀얼 GPU (비대칭 PCIe)** + mmproj 제약 + PP 동작을 모두 반영한 새로운 baseline.
---
## 12. 검증 절차 (재현용)
```bash
# 기동
run_variet_engine.bat
# 짧은 프롬프트 속도
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"balanced","messages":[{"role":"user","content":"Write 200 words about computing history."}],"max_tokens":300}' \
| python -c "import json,sys; d=json.load(sys.stdin); print(d['timings']['predicted_per_second'])"
# 긴 프롬프트 속도
python scripts/bench_long.py "verify"
# VRAM 확인
nvidia-smi --query-gpu=index,memory.used,memory.free,pcie.link.gen.current,pcie.link.width.current --format=csv
```
---
**Document sealed: 2026-04-11**