
LLM จริงๆแล้วทำงานยังไง ? เล่าเรื่อง Vector, Next-Token Prediction และ Fail-back Routing สไตล์ Dev
NEXT4I Developer
Founder & Software EngineerLLM จริงๆแล้วทำงานยังไง ? เล่าเรื่อง Vector, Next-Token Prediction และ Fail-back Routing สไตล์ Dev
ทำไมต้องมองว่า LLM เป็น probability engine ไม่ใช่สมอง และมันเปลี่ยน architecture ระบบยังไง เหตุผลเบื้องหลัง AI layer ของ NEXT4I
#LLM #Large Language Model #สถาปัตยกรรมระบบ #เอไอ #AIโมเดล``#โมเดลAI #Modelเอไอ #โมเดลเอไอ #เอไอ สเถียร
เคยสงสัยว่าทำไม model ตอบเลขผิดแบบมั่นใจสุดๆ นี่ไม่ใช่ bug code logic calculate อะไร แบบที่ dev เราคุ้นเคยครับ มันคือ model ทำงานตาม สิ่งที่มันควรจะเป็นแล้ว คือเดา token ถัดไปจากความน่าจะเป็น โดยไม่มีการคำนวณจริงๆ เกิดขึ้นข้างใต้เลย
TLDR; LLM (large language model) แปลงข้อความเป็น vector (พิกัดมิติตัวเลข ที่ใช้แทนความหมาย) แล้ว generate output ผ่าน next-token prediction โดย sample ด้วย parameter อย่าง top-k และ temperature เพราะมันคือ probability engine ไม่ใช่ calculator หรือ database ผมเลยออกแบบ NEXT4I ให้มี auto fail-back routing และเลือก model ตาม task โดยไม่ยึดติด model ตัวเดียวเป็น source of truth
Mental Model: Vector ไม่ใช่ Meaning
ทุก token ถูก embed เป็น vector บางทีมีหลายร้อยหรือหลายพันมิติ token ที่มีความหมายใกล้เคียงกันจะอยู่ใกล้กันในพื้นที่มิตินั้น นี่คือเหตุผลที่ semantic search (vector-based retrieval) match "large flying animal consumes insects" กับ "big bird eats worms" ได้ ทั้งที่ไม่มีคำตรงกันเลย ต่างจาก lexical search แบบเก่า (TF-IDF/BM25) ที่ต้องมีคำตรงกัน
GPU จัดการงานนี้ได้ดีเพราะมันถูกสร้างมาเพื่อ parallel floating-point math อยู่แล้ว (คณิตศาสตร์แบบเดียวกับที่ใช้ shade พิกเซลนับล้านต่อเฟรม) การโยน vector ทิศทางใกล้เคียงกันนับพันล้านตัวให้ GPU คำนวณ จึงเหมาะสมโดยธรรมชาติ ไม่ใช่เรื่องบังเอิญ
Generation คือการ Sample ไม่ใช่การ Retrieve
พอได้ prompt มา model ไม่ได้ไป lookup คำตอบครับ แต่มันจะ sample ทีละ token จาก probability distribution โดยมี 2 ตัวควบคุมหลักที่สำคัญ:
top_k: 2 # sample จากแค่ 2 token ที่มีโอกาสสูงสุด
temperature: 0.2 # ต่ำ = เป๊ะ/แน่นอน, สูง = สร้างสรรค์/หลากหลาย
Temperature ต่ำ + top_k ต่ำ จะได้ output ที่สม่ำเสมอ เหมาะกับงาน extract ข้อมูลแบบมีโครงสร้าง ส่วน Temperature สูงจะได้ความหลากหลาย เหมาะกับ brainstorm แต่ไม่เหมาะกับงานที่ต้องการความแม่นยำ
ทำไม LLM ถึงตอบผิดเวลาคำนวณเลข
ไม่มี calculator ซ่อนอยู่ใน model ครับ มันไม่ได้ evaluate x * y จริงๆ แต่ predict ตัวเลขที่ดูสมเหตุสมผลตาม prompt ทีละหลัก มันตอบ 2 * 2 ถูกเพราะ pattern นี้เจอบ่อยมากใน training data แต่มันตอบเลขคูณตัวใหญ่ผิดแบบมั่นใจ เพราะมันแค่ sample ตัวเลข ไม่ได้คำนวณจริง นี่คือเหตุผลที่ระบบ production สมัยนี้ ส่งงานคำนวณจริงไปที่ tool call (Python sandbox หรือ calculator function) แทนการเชื่อ output ดิบจาก model
Core Value: Generic Model-Tier Fail-back Router
นี่คือ pattern ที่ตัด business logic ออกทั้งหมด เป็น fail-back wrapper ที่ reuse ได้กับ model client กลุ่มไหนก็ได้ที่อยู่ tier เดียวกัน:
package modelrouter
import (
"context"
"errors"
"fmt"
)
// ModelClient คือ backend ไหนก็ได้ที่ตอบ prompt ได้
type ModelClient interface {
Name() string
Complete(ctx context.Context, prompt string) (string, error)
}
// TieredRouter ลองแต่ละ client ใน tier ตามลำดับจนกว่าจะสำเร็จ
type TieredRouter struct {
tier []ModelClient
}
func NewTieredRouter(clients ...ModelClient) *TieredRouter {
return &TieredRouter{tier: clients}
}
// Complete ลองทุก model ใน tier, fail-back เมื่อ error
func (r *TieredRouter) Complete(ctx context.Context, prompt string) (string, error) {
var errs []error
for _, client := range r.tier {
resp, err := client.Complete(ctx, prompt)
if err == nil {
return resp, nil
}
errs = append(errs, fmt.Errorf("%s: %w", client.Name(), err))
}
return "", errors.Join(errs...)
}
โครงสร้างนี้ตั้งใจออกแบบให้เรียบง่ายที่สุดครับ: ลอง model ตัวถัดไปใน tier เดียวกันเมื่อเกิด error และ return ตัวแรกที่ทำงานสำเร็จ โดยยังไม่ต้องมี retry with backoff หรือ circuit breaker เพื่อเน้น core idea ของ fail-back เท่านั้น ใน implementation จริงของ NEXT4I tier จะถูก populate แบบ dynamic และนำ health state มาจัดลำดับด้วย แต่ส่วนนั้นถูก abstract ออกไป เพราะ code generic ด้านบนคือส่วนที่มีประโยชน์ต่อการนำไปปรับใช้จริงมากที่สุดครับ
Core Value: Task-Based Routing แบบเข้าใจง่าย
Router ขนาดกะทัดรัดที่เช็ค task complexity ก่อนเลือก tier:
package modelrouter
type Complexity int
const (
Simple Complexity = iota
Complex
)
func ClassifyAndRoute(task string, simpleTier, complexTier *TieredRouter) *TieredRouter {
if estimateComplexity(task) == Simple {
return simpleTier
}
return complexTier
}
func estimateComplexity(task string) Complexity {
if len(task) < 100 {
return Simple
}
return Complex
}
EstimateComplexity สามารถทำแบบง่ายๆ ด้วย length/keyword heuristic หรือขยับไปใช้ classifier model เล็กๆ ก็ได้ ประเด็นสำคัญคือ routing decision ต้องเกิดขึ้นก่อนเรียก model ที่มี cost สูง
Trade-off ที่ผมเลือก
- Fail-back แค่ใน tier เดียวกัน ไม่ข้าม tier: ถ้าสลับจาก model ขนาดใหญ่ไปเป็น model ขนาดเล็กแบบเงียบๆ คุณภาพ output จะเปลี่ยนไปโดยผู้ใช้ไม่รู้ตัว การแบ่ง tier มีไว้เพื่อป้องกันปัญหานี้โดยเฉพาะ
- Router ไม่มี cross-request state: ทำให้ stateless และ horizontal scale ได้ง่ายมาก แต่แลกกับการที่มันไม่จำ failure ที่ผ่านมาข้าม lifecycle ของ request
- Routing เป็น toggle ไม่ได้ required: ผู้ใช้ยังสามารถระบุ model เจาะจงได้ หากต้องการ deterministic behavior จาก provider ใดเป็นพิเศษ โดย router จะทำงานเป็นค่าเริ่มต้นเท่านั้น
และท้ายที่สุด: อยากให้มองว่า model คือ probability engine ที่เราไม่ควรยึดติด 100% แล้วปล่อยให้ architecture รอบตัวมัน ไม่ว่าจะเป็น fail-back, routing หรือ tool call เข้ามารับผิดชอบเรื่องความแม่นยำและความเสถียรแทนครับ
ขอบคุณทุกท่านที่อ่านมาจนถึงตรงนี้ และผมจะพยายามทำบทความดีๆ ขึ้นมาเรื่อยๆ นะครับ
ติดตามการเดินทางของ NEXT4I ได้โดยตรงผ่านเว็บไซต์นี้ และสามารถลงทะเบียนเพื่อทดลองใช้ผลิตภัณฑ์ → ได้ที่นี่
Related Articles
All Dev Notes

ทำไม PDF ถึงไม่น่ารักกับ AI และการ RAG ด้วย PDF มันวุ่นวายและซับซ้อนขนาดไหน ทำไมผมเททั้งใจให้ Markdown

ผมสร้าง "สมองที่สอง" ด้วย Obsidian ที่ AI Agent อ่านได้ โดยไม่ต้องสร้าง Custom RAG Pipeline
Be the first to try it
ลงชื่อเพื่อรับแจ้งเตือน และร่วมเป็นผู้ใช้งานกลุ่มแรกพร้อมรับสิทธิพิเศษ
Drop your email to get notified. Early access members get exclusive perks!
We hate spam as much as you do. Only big updates, no junk.
No subscriptions. No annual fees. No lock-ins.
NEXT4I