บทเรียนจากการลอง 7 โมเดลบน RTX 5060 Ti 16GB
เปิด OpenWebUI ให้ทีมใช้ผ่าน internet, ต่อ Claude Code เข้า local model, แล้วเสิร์ฟ API แบบ OpenAI ให้ WordPress — ทั้งหมดบนการ์ดใบเดียว พร้อมหลุมพรางที่เจอจริงระหว่างทาง
ทำไมต้อง Local LLM
ผมอยากได้ระบบ AI ส่วนตัวที่: คุยได้ผ่านเว็บแบบ ChatGPT, ค้นเว็บและตอบจากเอกสารของเราเองได้, ต่อกับ Claude Code ไว้ช่วยเขียนโค้ด, และเปิด API ให้แอปอื่น (เช่น WordPress) เรียกไปใช้ generate ข้อความ — โดยข้อมูลไม่ต้องออกไปไหน ไม่มีค่า subscription รายเดือน
ฮาร์ดแวร์ที่มี: Ubuntu, การ์ดจอ RTX 5060 Ti VRAM 16GB ฟังดูเหมือนพอ แต่ตัวเลข 16GB นี่แหละที่กลายเป็นตัวกำหนดทุกการตัดสินใจตลอดทาง
สิ่งที่ผมเรียนรู้คือ การ setup ส่วน “ให้มันทำงาน” ใช้เวลาไม่นาน แต่การ “ทำให้มันทำงานได้ดีจริง” ต้องผ่านการลองผิดลองถูกเยอะมาก โดยเฉพาะเรื่องเลือกโมเดลให้เหมาะกับ VRAM ที่มี บทความนี้เลยเล่าทั้งวิธีทำและหลุมที่ตกลงไป เผื่อใครจะทำตามจะได้ข้ามหลุมเหล่านี้
ส่วนที่ 1: วาง stack หลัก — Ollama + OpenWebUI

ตัวเลือก framework ที่ลงตัวที่สุดสำหรับผู้เริ่มต้นคือ Ollama — ติดตั้งง่าย จัดการ model ง่าย มี REST API ในตัว และเข้ากับ OpenWebUI ได้ตรงๆ
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:14b
จากนั้นรัน OpenWebUI ผ่าน Docker เป็นหน้าเว็บสำหรับใช้งาน และต่อ Cloudflare Tunnel เพื่อเปิดให้เข้าถึงจาก internet ได้โดยไม่ต้อง forward port (tunnel เป็น outbound connection จึงทำงานได้แม้อยู่หลัง NAT หรือ IP สาธารณะเปลี่ยน)
หลุมที่ 1: container เชื่อม Ollama ไม่ได้
อาการแรกที่เจอคือ OpenWebUI ขึ้น “Ollama: Network Problem” ทั้งที่ Ollama รันอยู่ ไล่เช็คจนเจอว่า OpenWebUI ใน container ยิงไปที่ host ผ่าน host.docker.internal แต่ packet ถูก UFW (firewall) ดรอป เพราะมีแต่ rule allow port 22 (อาจเป็นเพราะ IPv6 ?)
วิธีแก้คืออนุญาต traffic จาก docker subnet:
sudo ufw allow from 172.16.0.0/12 to any port 11434
sudo ufw reload
บทเรียน: ถ้า container เชื่อม service บน host ไม่ได้ และ curl จากใน container ขึ้น Connection timed out (ไม่ใช่ refused) — มักเป็น firewall ดรอป ไม่ใช่ service ไม่ทำงาน
หลุมที่ 2: ดึง Docker image ไม่ได้
ตอนจะติดตั้ง SearXNG (search engine สำหรับ web search) เจอ TLS handshake timeout เวลา pull จาก Docker Hub ทั้งที่ image จาก ghcr.io ดึงได้ปกติ ต้นเหตุคือ DNS ของ ISP resolve CDN ของ docker.io ได้ไม่ดี แก้โดยตั้ง DNS ของ Docker daemon ตรงๆ:
echo '{"dns": ["1.1.1.1", "8.8.8.8"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
แค่นี้ก็ดึง image ได้ บทเรียน: ปัญหา network ของ Docker หลายอย่างแก้ที่ DNS ได้โดยไม่ต้องเปลี่ยนเน็ต
ส่วนที่ 2: ทำให้ฉลาดขึ้น — Web Search + RAG

OpenWebUI มี web search และ RAG (ตอบจากเอกสาร) ในตัว แค่เปิดใช้และตั้งค่า SearXNG เป็น search engine แต่พอลองค้นจริง เจออาการประหลาด: ค้นเจอ แต่สรุปมั่ว บางทีก็ขึ้น “No search result” แล้วตอบจากความรู้เก่า
ไล่ดู log ของ Ollama เจอบรรทัดชี้เป็น: task.n_tokens = 23 — แปลว่าผลค้นหาไม่ได้ถูกส่งเข้า model เลย model เห็นแค่คำถามเปล่าๆ
ต้นเหตุจริงคือ embedding model พัง (error 500) ทำให้ผลค้นหา embed ไม่ได้ → vector DB ว่าง → retrieve ได้ 0 ท่อน วิธีแก้คือเปลี่ยน embedding model เป็น bge-m3 ซึ่งรองรับภาษาไทยได้ดีด้วย:
ollama pull bge-m3
ตั้งใน Admin Settings → Documents → Embedding Model เป็น bge-m3 แล้วลองใหม่ คราวนี้ log ขึ้น n_tokens = 16324, truncated = 0 — ผลค้นหาเข้า model ครบ สรุปได้ถูกต้อง
อีกตัวที่สำคัญคือ num_ctx (context window) — Ollama ตั้ง default ไว้แค่ 4096 ซึ่งเล็กเกินไปสำหรับ web search ที่ต้องยัดผลหลายหน้าเข้า context ต้องขยายเป็น 16384–32768 ผ่าน Advanced Params ของ model
ส่วนที่ 3: บทเรียนใหญ่ — เลือกโมเดลให้เหมาะกับ VRAM
นี่คือส่วนที่ใช้เวลามากที่สุด และได้บทเรียนเยอะที่สุด ผมลองโมเดลไป 7 ตัวกว่าจะเจอตัวที่ใช่ ขอสรุปเป็นตารางก่อน:
| Model | ขนาด | ลง GPU เต็ม? | eval rate | ผลสรุป |
|---|---|---|---|---|
| qwen3.5:9b | 6.6GB | ✅ 100% | ~70 tok/s | เร็วสุด |
| qwen3:14b | 9.3GB | ✅ 100% | ~37 tok/s | สมดุล ฉลาดดี |
| gemma4:e4b | 9.6GB | ✅ 100% | ~50 tok/s | มั่ว, output overflow |
| glm-4.7-flash | 19GB | ❌ offload 26% | ~50 tok/s | MoE เร็วแม้ offload แต่ prompt eval ผันผวน |
| batiai/qwen3.6-27b:q3 | 13–19GB | ⚠️ ขึ้นกับ context | ~26 tok/s | offload ถ้า context ใหญ่ |
| qwen3.6:27b | 17GB | ❌ offload หนัก | ~14 tok/s | ช้าเกินไป |
บทเรียน A: dense model ที่ใหญ่เกิน VRAM = ช้าทันที
qwen3.6:27b (17GB) ใหญ่กว่า VRAM 16GB ทำให้ Ollama ต้อง offload บางส่วนไป CPU (ollama ps ขึ้น 12%/88% CPU/GPU) ผลคือ eval rate ดิ่งจาก ~70 เหลือ 14 tok/s ตอบคำถามที่ต้องเขียนยาวๆ ใช้เวลาเป็นนาที
บทเรียน B: context window กิน VRAM มหาศาล
เคสที่ทำให้ผมเข้าใจเรื่องนี้ชัดสุดคือ batiai/qwen3.6-27b:q3 ตัว model เองแค่ 13GB (ลง GPU ได้) แต่พอ default context เป็น 128K ตัว KV cache บวมจน SIZE รวมเป็น 19GB → offload → ช้า พอลด context เหลือ 16K ตัวเดียวกันกลับมาเป็น 100% GPU และเร็วขึ้นเกือบ 3 เท่า
บทเรียน: บนการ์ด VRAM จำกัด คุณต้องเลือกระหว่าง “params เยอะ” กับ “context ใหญ่” — เลือกได้อย่างเดียว
บทเรียน C: thinking mode ทำให้ “รู้สึก” ช้า
ผมเคยสงสัยว่าทำไม Ollama บน Ubuntu รู้สึกช้ากว่าบน Windows ทั้งที่ใช้ CUDA เหมือนกัน (เช็คจาก log ว่าใช้ CUDA จริง ไม่ใช่ Vulkan) คำตอบคือ thinking mode — model ตระกูล qwen3 จะ “คิด” ก่อนตอบ ถาม “Hello” คำเดียวมันคิด 259 tokens ก่อนตอบ 10 tokens
ทดสอบเทียบ: ถาม “Hello” บน qwen3.5:9b เปิด thinking ใช้ 4.2 วินาที, ปิด thinking (/set nothink ใน CLI) เหลือ 0.87 วินาที — eval rate เท่าเดิม (~70 tok/s) แต่ total duration ต่างกัน 5 เท่า เพราะจำนวน token ที่ต้อง generate ต่างกัน
บทเรียน: GPU ความเร็วเท่าเดิม แต่ thinking ทำให้ต้องผลิต token เยอะกว่ามากต่อคำตอบ สำหรับงาน chat ทั่วไปปิดได้เลย คุ้มกว่า
ส่วนที่ 4: ต่อ Claude Code เข้า local model
Ollama v0.14+ รองรับ Anthropic Messages API แล้ว Claude Code จึงต่อกับ local model ได้โดยตรงไม่ต้องใช้ proxy แค่ตั้ง environment variables:
export ANTHROPIC_BASE_URL=http://localhost:11434
export ANTHROPIC_AUTH_TOKEN=ollama
export ANTHROPIC_API_KEY=""
export ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3.5:9b
# ตั้ง HAIKU และ OPUS เป็น model เดียวกันด้วย เพราะ Claude Code route ไปหลาย tier
หลุมที่ 3: tool calling format ต้องถูก
ปัญหาที่ปวดหัวสุดคือ บาง model สั่งให้ “สร้างไฟล์ index.html” แล้วมันพ่นโค้ดออกมาเป็นข้อความแทนที่จะสร้างไฟล์จริง
วิธี debug ที่ได้ผลคือ ยิง API ใส่ tool definition ตรงๆ แล้วดูว่า response มี field tool_calls แยกออกมา (ถูก) หรือ JSON ปนอยู่ใน content (ผิด):
curl http://localhost:11434/api/chat -d '{
"model": "MODEL_NAME",
"messages": [{"role":"user","content":"create index.html"}],
"tools": [{"type":"function","function":{"name":"write_file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}}}}}],
"stream": false
}'
ผลที่เจอน่าสนใจมาก: qwen2.5-coder (coding model แท้ๆ) กลับพ่น tool call เป็นข้อความใน content — Claude Code parse ไม่ได้ ส่วน qwen3.5:9b และ qwen3:14b ให้ tool_calls ถูกต้อง
บทเรียน: สำหรับ Claude Code ปัจจัยสำคัญไม่ใช่ว่าเป็น “coding model” หรือไม่ แต่คือ tool-call format ถูกต้องไหม — ทดสอบด้วย curl ก่อนเสมอ
ผู้ชนะ: qwen3.5:9b
หลังลองมาทั้งหมด qwen3.5:9b กลายเป็นตัวที่ลงตัวสุดสำหรับ Claude Code บนการ์ดนี้ เพราะ:
- ขนาดแค่ 6.6GB → เหลือ VRAM ~8GB เอาไปทำ context ได้ถึง 64K (ที่ Claude Code แนะนำ) โดยยังลง GPU เต็ม
- tool-call format ถูกต้อง
- เร็ว ไม่ offload → ไม่ timeout
- ที่เซอร์ไพรส์สุด: ทดสอบสั่ง “Create HTML Hello World” โดยไม่บอกให้ save และไม่มี CLAUDE.md ช่วย — มันเข้าใจ intent เอง สร้างไฟล์ hello.html ให้ พร้อมตั้งชื่อไฟล์เอง
บทเรียน: VRAM ที่ “เหลือเยอะ” ไม่ได้เสียเปล่า — มันเอาไปทำ context window ใหญ่ได้ ซึ่ง model ใหญ่ที่กิน VRAM หมดทำไม่ได้
หลุมที่ 4: Cloudflare Tunnel มี timeout 100 วินาที
ตอนเปิด Claude Code ให้เรียก API ผ่าน tunnel จากเครื่องนอก เจอ Error 524 — Cloudflare มี hard limit ที่ ~100 วินาทีต่อ request ถ้า model ตอบไม่ทันจะถูกตัด โมเดลที่ offload (ตอบช้า) จะชน limit นี้บ่อย แต่ qwen3.5:9b ที่เร็วและไม่ offload ผ่านได้สบาย
สำหรับการเปิด API ออก internet ผมใช้ nginx เป็น reverse proxy หน้า Ollama แล้วบังคับ Bearer token auth (เพราะ Ollama API ไม่มี authentication ในตัว — ห้ามเปิด port 11434 ออก internet ตรงๆ เด็ดขาด):
server {
listen 8443;
location / {
if ($http_authorization != "Bearer YOUR_SECRET_TOKEN") {
return 401 '{"error":"unauthorized"}';
}
proxy_pass http://127.0.0.1:11434;
proxy_buffering off;
proxy_read_timeout 600s;
}
}
ส่วนที่ 5: เสิร์ฟ API แบบ OpenAI ให้ WordPress
ข้อดีของ Ollama คือ expose endpoint หลายแบบพร้อมกันบน port เดียว — /v1/messages (Anthropic format สำหรับ Claude Code) และ /v1/chat/completions (OpenAI format) ดังนั้น nginx + Bearer auth ตัวเดียวรองรับได้ทั้ง Claude Code, WordPress, และแอปอื่นๆ
หลุมที่ 5: thinking ของ qwen3.5 ปิดยากผ่าน API
WordPress plugin ขอ generate ข้อความ แต่ qwen3.5 ดัน thinking ทำให้ response มี field reasoning ยาวเป็นพัน token ทั้งที่คำตอบจริงแค่ 3 ประโยค ปัญหาคือ:
SYSTEM /no_thinkใน Modelfile ไม่ทำงานเมื่อ client ส่ง system prompt เองPARAMETER think falseใน Modelfile ยังไม่รองรับ (error)- วิธีเดียวที่ได้ผลคือส่ง
think: falseใน API request — แต่ WordPress plugin หลายตัวใส่ custom param ไม่ได้
ทางออกที่ practical สำหรับงาน generate text คือใช้ model ที่ ไม่มี thinking ตั้งแต่แรก เช่น qwen2.5 ทั่วไป (generation เก่ากว่า ตอบตรงๆ ไม่มี reasoning กวน) — แยกหน้าที่ชัดเจน: qwen3.5:9b สำหรับ Claude Code, qwen2.5 สำหรับงานเขียนผ่าน WordPress
สรุป
หลังผ่านหลุมทั้งหมด ระบบที่ได้คือ:
- OpenWebUI + web search + RAG สำหรับ chat ทั่วไป เปิดให้ทีมใช้ผ่าน Cloudflare Tunnel
- Claude Code ต่อ qwen3.5:9b (context 64K) สำหรับช่วยเขียนโค้ด
- OpenAI-compatible API ให้ WordPress และแอปอื่นเรียก generate ข้อความ
- ทั้งหมดผ่าน nginx + Bearer auth, ทุก service auto-start หลัง reboot
บทเรียนที่สำคัญที่สุดสำหรับใครที่มีการ์ด 16GB:
- เลือก model ที่ลง GPU เต็ม สำคัญกว่าเลือก model ที่ params เยอะ — model เล็กที่เร็วและไม่ offload ชนะ model ใหญ่ที่ offload เสมอ
- VRAM ที่เหลือไม่เสียเปล่า — เอาไปทำ context window ใหญ่ได้
- ทดสอบ tool-call format ด้วย curl ก่อนสรุปว่า model ใช้กับ Claude Code ได้
- ปิด thinking สำหรับงานที่ไม่ต้องการเหตุผลหลายขั้น — ช่วยเรื่องความเร็วและ token usage มาก
- อย่าเปิด Ollama API ออก internet ตรงๆ — ครอบด้วย auth เสมอ
Local LLM ในปี 2026 ดีพอสำหรับงานจริงแล้ว ไม่ใช่แค่ของเล่น แต่กุญแจสำคัญคือการจับคู่ model กับฮาร์ดแวร์ให้ถูก — ซึ่งบางทีก็ต้องลองหลายตัวกว่าจะเจอตัวที่ใช่


