THEN
PART 2 · ติดตั้ง

Mac Mini = สมอง + VPS = หน้าบ้าน

Mac Mini (Ollama MLX + 27B) and VPS bootstrap

TL;DR — สรุปใน 30 วินาทีtldr

เป้าหมายของบทนี้: ทำให้ Mac กลายเป็น "สมอง" — ติดตั้ง Ollama กับ backend ที่เร็วที่สุดของ Apple Silicon คือ MLX แล้วโหลดโมเดล 27B ให้พร้อมตอบ 30 tok/s ตลอด 24 ชม.

สิ่งที่ได้ตอนจบบท: โมเดลหลัก resident 23GB + embeddings bge-m3 อุ่นอยู่ พร้อม alias 5 ตัว (chat / think / code / deep / fast) ที่สลับกันได้ฟรี นะครับ

VPS คือ "หน้าบ้าน" — จุดที่โลกภายนอกเห็น มีหน้าที่ 3 อย่าง: รับ HTTPS, ตรวจกุญแจและงบประมาณ, เก็บฐานข้อมูล/ค้นเว็บ ทั้งหมดนี้รันเป็น Docker containers หกตัวที่ยกขึ้นด้วยคำสั่งเดียว

Serviceหน้าที่เหตุผลที่มี
LiteLLMประตู API — กุญแจ/งบ/เส้นทาง/fallbackหัวใจการคุมทุก call บันทึกค่าใช้จ่ายทุกครั้ง
Open WebUIเว็บแชทสำหรับผู้ใช้หน้าบ้านที่คนใช้จริงเห็น
Postgres 16ฐานข้อมูลหลักเก็บ spend logs + ข้อมูลผู้ใช้
Qdrant 1.19เวกเตอร์สำหรับ RAGคลังความรู้ภายใน อยู่ localhost เท่านั้น
Valkey 8แคชลดงานซ้ำ
SearXNGเครื่องมือค้นเว็บของตัวเองแทนจ่ายค่า search API — localhost เท่านั้น

เมื่อไหร่ควรใช้ / When to usewhen to use

ทำบทนี้เมื่อซื้อ/เตรียม Mac เสร็จและผ่านบทที่ ๒ (เครือข่าย) แล้ว — Mac ควรต่อไฟตลอด ปิด sleep (หรือตั้ง wake-on-network) เพราะเป็นสมองกลาง 24 ชมนะครับ.

  • เวอร์ชันที่เราใช้จริงและยึดตาม: Ollama 0.34.2 (backend MLX) — อย่าใช้ latest แบบไม่ระบุเวอร์ชัน — ตรวจหน้า release ประกอบวันที่ที่คู่มือตรวจ (บทที่ ๙)
  • โมเดลที่ใช้จริง: กลุ่ม 27B Qwen fine-tune ชุด TURBO-Fable (โดย DavidAU) แบบ nvfp4 — ดึงจาก Hugging Face ของผู้สร้างโดยตรง

ทำบทนี้หลังเครือข่าย (บทที่ ๒) พร้อม — VPS สเปกขั้นต่ำ 4 vCPU / 8GB / Debian 12 หรือ Ubuntu ก็เพียงพอ (ของเราคือ 12 vCPU / 47GB เพราะแชร์กับงานอื่น) นะครับ

  • ต้องมี Docker + compose plugin (ติดตั้งตาม docs.docker.com สำหรับ Linux)
  • ต้องมี Tailscale บน VPS แล้ว (จด Tailscale IP ของ Mac จากบทที่ ๒ ไว้ใส่ .env ในบทถัดไป)
  • ต้องมีโดเมนจด DNS ไว้ที่ Cloudflare แล้ว (สำหรับ tunnel จากบทที่ ๒)

วิธีทำ / How-tohow to

ขั้น 1 — ติดตั้ง Ollama แบบ service

# ติดตั้งผ่าน Homebrew (macOS)
brew install ollama

# ให้รันเป็น service เปิดตลอด
brew services start ollama

# ตรวจเวอร์ชัน ต้องได้ ~0.34.x
ollama --version

ถ้าอยากเก็บโมเดลบน external SSD: ตั้งค่า OLLAMA_MODELS ชี้ไปที่ volume นั้นก่อน pull แรกนะครับ

ขั้น 2 — โหลดน้ำหนักโมเดลจาก Hugging Face

# ดึงน้ำหนัก 27B แบบ MLX nvfp4 จาก repo ของผู้สร้าง (~18GB)
ollama pull hf.co/<ผู้สร้าง>/<ชุดโมเดล>:<แท็ก nvfp4>

# สร้าง manifest หลักจากไฟล์ตัวอย่างของ repo (แก้ FROM ให้ชี้แท็กที่ pull มา)
ollama create qwen38-chat -f configs/ollama-modelfile.example

# สร้าง alias ที่แชร์ manifest เดียวกัน (สลับได้ฟรี ไม่ reload)
ollama cp qwen38-chat qwen38-think
ollama cp qwen38-chat qwen38-code
ollama cp qwen38-chat qwen38-deep
ollama cp qwen38-chat qwen38-fast

น้ำหนักโมเดลไม่ได้แจกใน repo — ดึงจาก HF ของผู้สร้างเสมอ (เรื่องลิขสิทธิ์) ครับ

ขั้น 3 — ตรวจว่าสมองพร้อม

ollama ps
# NAME                  SIZE      PROCESSOR   UNTIL
# qwen38-chat:latest    23 GB     100% GPU    23 hours from now
# bge-m3:latest         1.1 GB    100% GPU    3 hours from now

curl http://100.x.y.z:11434/api/tags   # จากเครื่องอื่นใน mesh ต้องเห็นรายชื่อโมเดล

ถ้า SIZE ไม่ขึ้น ~23GB หรือ PROCESSOR ไม่เป็น 100% GPU ให้ย้อนดู backend MLX ก่อนนะครับ

ถ้าติดตรงไหน → บทที่ ๙

ของดีของ kit นี้คือสคริปต์ bootstrap.sh — ตรวจทุกอย่างให้ก่อน แล้วยกทั้ง stack ด้วยคำสั่งเดียว ปลอดภัยรันซ้ำได้:

# โคลน repo แล้วเตรียมไฟล์ความลับ
git clone https://github.com/siamcafe/bomllm.git
cd bomllm
cp configs/.env.example .env      # แล้วเปิดแก้ค่า CHANGE_ME ทุกตัว (บทที่ ๔) ครับ

# ยก stack ทั้งหมด (idempotent — รันซ้ำได้)
./scripts/bootstrap.sh

# หรือดูก่อนว่าจะทำอะไร โดยไม่แต๊ะอะไรจริง
./scripts/bootstrap.sh --dry-run

สคริปต์จะ: ตรวจ .env chmod 600, ห้ามค่า CHANGE_ME หลงเหลือ, สร้าง litellm.yaml จากตัวอย่าง, seed SearXNG, pull รูปที่ปักเวอร์ชัน, up -d, รอ LiteLLM alive, แล้วรัน smoke gate นะครับ

สิ่งที่ต้องทำเองหลัง bootstrap (สคริปต์พิมพ์บอกตอนจบ) ครับ:

  1. ชี้ Cloudflare Tunnel: chat ชื่อโดเมน → localhost:3000 และ llm ชื่อโดเมน → localhost:4000 (ห้ามเปิดพอร์ตสาธารณะโดยตรง)
  2. สร้างโมเดลบน Mac ตามบทที่ ๓ (ถ้ายังไม่ทำ)
  3. สร้างกุญแจรายช่องทาง — บทที่ ๔
  4. รันชุดตรวจ 6 ข้อเต็ม — บทที่ ๙

ผลจากเครื่องจริงหลังยกสำเร็จครับ:

vps — ตัวอย่างจากเอกสาร (sanitized) · docker compose ps
NAME SERVICE STATUS PORTSbomllm-litellm-1 litellm Up 26 days 127.0.0.1:4000->4000/tcpbomllm-open-webui-1 open-webui Up 26 days 127.0.0.1:3000->3000/tcpbomllm-postgres-1 postgres Up 26 days 5432/tcpbomllm-qdrant-1 qdrant Up 26 days 6333/tcpbomllm-valkey-1 valkey Up 26 days 6379/tcpbomllm-searxng-1 searxng Up 26 days 127.0.0.1:8888->8080/tcp# สังเกต: พอร์ตสาธารณะ = ไม่มีเลย ทุกตัว bind 127.0.0.1

ที่มา: หน้า components ของคู่มือนี้ (สร้างจากเอกสาร repo ฉบับ sanitized) ครับ

ถ้าติดตรงไหน → บทที่ ๙

ตัวอย่างการตั้งค่า / Config exampleconfig example

ไฟล์ configs/ollama-modelfile.example ฉบับเต็ม (จาก repo) พร้อมคำอธิบายไทยรายบรรทัด:

# FROM = ชี้ไปที่แท็กน้ำหนักที่คุณ pull มา (เปลี่ยนเป็นของคุณ)
FROM turbo-fable-nvfp4

# พื้นระบับ context สำหรับ alias ทุกตัว — ห้ามตั้งต่ำกว่านี้
# [บทเรียนจริง] พลาดบรรทัดนี้ครั้งหนึ่ง ผู้เรียกตรง (ไม่ผ่าน proxy) จะหล่นไป 8192 เงียบ ๆ
PARAMETER num_ctx 32768

# sampling ตั้งน้อยๆ ที่นี่ — ค่าจริงใช้ route ของ LiteLLM กำหนดทับได้ทุก request
PARAMETER temperature 0.7
PARAMETER top_p 0.8
PARAMETER top_k 20
PARAMETER repeat_penalty 1.05

# persona ขั้นต่ำสำหรับผู้เรียกตรง (สูตรเต็ม Config G อยู่บทที่ ๖ และใช้ที่ route ไม่ใช่ที่นี่)
SYSTEM """You are a helpful assistant. Answer in the language of the user's
most recent message unless instructed otherwise. Never emit Chinese characters
unless the user asks about Chinese. Be concise; admit uncertainty."""

กฎสำคัญ: หนึ่ง manifest ต่อโมเดล — พฤติกรรมต่างกันไปตั้งที่ route ของ LiteLLM ไม่ใช่แยก Modelfile กันคนละไฟล์ นะครับ

หัวใจของหน้าบ้านคือ docker-compose.yml — ส่วนที่สำคัญที่สุดพร้อมคำอธิบายไทย:

services:
  litellm:
    image: ghcr.io/berriai/litellm:main-stable   # ปักแท็ก ห้าม latest ครับ
    environment:
      # ความลับทุกตัวมาจาก .env ทั้งสิ้น ไม่มีค่าจริงในไฟล์นี้
      LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY:?set in .env}
      # Mac ผ่าน Tailscale เท่านั้น — ห้ามเป็น IP สาธารณะเด็ดขาด
      MAC_OLLAMA_BASE_URL: ${MAC_OLLAMA_BASE_URL:?e.g. http://100.x.y.z:11434}
    volumes:
      - ./configs/litellm.yaml:/app/config.yaml:ro   # config จริง mount แบบอ่านอย่างเดียวนะครับ
    ports:
      - "127.0.0.1:4000:4000"   # หน้าบ้านคุมด้วย tunnel ห้าม publish ครับ

  open-webui:
    image: ghcr.io/open-webui/open-webui:v0.11.3
    environment:
      OPENAI_API_BASE_URLS: "http://litellm:4000/v1"  # เดินผ่าน proxy เท่านั้นครับ
      OLLAMA_BASE_URLS: ""      # ว่างโดยตั้งใจ: ห้าม WebUI ข้าม proxy ไป Mac ตรง ๆ นะครับ
      ENABLE_WEB_SEARCH: "true" # ค้นเว็บใช้ SearXNG ในเครื่อง (ประหยัดค่า API) ครับ
      RAG_EMBEDDING_ENGINE: ollama
      RAG_EMBEDDING_MODEL: bge-m3

ฉบับเต็มพร้อมทุก service อยู่ใน repo — ไฟล์นี้ออกแบบให้ copy ไปใช้ได้เลย ไม่มีความลับฝังอยู่ นะครับ

Pro tip — เคล็ดลับจากสนามจริงpro tip

★ PROD LESSON
กับดัก alias ของ MLX — backend MLX ไม่แชร์ runner ระหว่าง alias ที่ "manifest ต่างกัน" — สลับ alias จะเสีย ~1.2 วินาที reload ต่อครั้ง (ครั้งแรกหลังจัด context ใหม่ 16 วินาที) แต่ถ้าเป็น manifest เดียวกัน (ทำด้วย ollama cp) สลับฟรี ~0.8 วิ — นี่คือเหตุผลที่คู่มือยืนยันให้ใช้ ollama cp สร้าง alias ทั้งหมด
★ PRO TIP
อย่าให้ health check ปลุกโมเดลที่ไล่ออก — เราเคยโดน /health sweep ของ LiteLLM โหลดโมเดล 16GB ที่เพิ่งไล่ออกกลับมาเอง ~24 ชม. จน swap thrash — ทางแก้คือปิด background health check บน route เย็น ๆ (ตั้งใน litellm.yaml บทที่ ๔)
★ PROD LESSON
ประตูเดียวที่ตรวจได้ — ให้ทุกอย่างเดินผ่าน LiteLLM แม้แต่เว็บแชท (ตั้ง OLLAMA_BASE_URLS ว่างไว้) — เพราะสิ่งที่ไม่ผ่านประตูคือสิ่งที่วัดไม่ได้ งบประมาณกุมไม่ได้ และ log ไม่มี นี่คือหลักออกแบบข้อแรกของทั้งระบบ
★ PRO TIP
ปักเวอร์ชันเสมอ — ทุก image ใน compose เป็นแท็กเจาะจง (postgres:16-alpine, qdrant:v1.19.1, searxng แบบ dated tag) — เราเคยโดน latest เปลี่ยนพฤติกรรมใต้มือจนนอนไม่หลับ ตั้งแต่นั้นมา latest เป็นคำหยาบในทีม

คำถามที่พบบ่อย / FAQfaq

ถาม: ทำไมต้อง 27B ทำไมไม่ใช้ตัวเล็กกว่า? — เล็กกว่านี้คุณภาพภาษาไทยตกชัดเจนในการทดสอบของเรา ส่วนใหญ่กว่านี้ (32B+) จะกิน RAM จนเสี่ยง swap — 27B บน 48GB คือจุดสมดุลที่วัดแล้ว

ถาม: TURBO-Fable คืออะไร ปลอดภัยไหม? — เป็นชุด fine-tune ของผู้พัฒนาอิสระ (DavidAU) บนน้ำหนักเดิมของ Qwen เราทดสอบสถิติแล้วว่าภาษาไทยไม่ถดถอย (CI รวมศูนย์) และตัวอักษรจีนหลุดน้อยกว่าต้นฉบับซะอีก

คำถามอื่น ๆ ดูที่ FAQ

ถาม: รันบน VPS บ้านเรา (ไทย) ได้ไหม? — ได้ แต่แนะนำสิงคโปร์เพราะใกล้ผู้ใช้ไทย หน่วงต่ำ ราคาใกล้กัน ส่วน Mac ที่บ้านก็คุยกับ VPS ผ่าน Tailscale ได้ไม่ว่าอยู่ไหน

ถาม: VPS คือค่าใช้จ่ายลับที่ไม่ได้บวกใน $0.54? — ใช่ ค่า VPS ~$10–15/เดือนคิดแยกเพราะของเราโฮสต์งานอื่นด้วย ถ้าเช่าใหม่เพื่อ BOMLLM ล้วน ๆ ให้บวกเข้าต้นทุนรายวัน ~$0.4

ดูเพิ่ม / See alsosee also

ภาพประกอบ / Figuresfigures

เส้นทาง request จากเว็บแชทถึงสมอง Mac
VPS ไม่เคยเรียก Mac ตรง ๆ นอก mesh
กล่อง VPS ในแผนภาพสถาปัตยกรรม
6 containers ในเครือข่าย Docker เดียว (bomllm)

คลิปหน้าจอจริงตามบทเรียน · คลิกเล่นเพื่อฟังสรุป · ไม่ใช่คำแนะนำการลงทุนครับ

▶ ดูวิดีโอคู่มือ: https://youtu.be/O4hM39cgDCY