นอกจากน้ำหนักแล้ว มีอะไรอยู่ใน GGUF และยังขาดอะไรอยู่บ้าง?
(nobodywho.ooo)- GGUF คือรูปแบบไฟล์โมเดลภาษาที่
llama.cppใช้งาน โดยรวมเมทาดาทาที่จำเป็นต่อการรันไว้ใน ไฟล์เดียว เพื่อให้การแจกจ่ายและการโหลดโมเดลง่ายขึ้น - เทมเพลตแชต เป็นสคริปต์ Jinja2 ที่จัดการรูปแบบบทสนทนา การเรียกใช้เครื่องมือ และการเข้ารหัสข้อความมัลติมีเดีย แต่พฤติกรรมยังต่างกันไปตามแต่ละ implementation
- GGUF สามารถเก็บ โทเค็นพิเศษ เช่นโทเค็นจบการทำงาน และค่าตั้งค่า sampler ที่แนะนำได้ และล่าสุดยังสามารถระบุ ลำดับของ sampler chain ได้แล้ว
- ตอนนี้ รูปแบบการเรียกใช้เครื่องมือ ยังแตกต่างกันไปตามแต่ละโมเดล จึงยังต้องมีการฮาร์ดโค้ดตาม inference engine และการสร้าง parser จากไวยากรณ์ยังเป็นตัวเลือกสำหรับการปรับปรุงมาตรฐานในอนาคต
- ยังขาด
think_token, การ bundle projection model และ feature flags ทำให้การแยกช่วงความคิด การจัดองค์ประกอบแบบมัลติโหมด และการตรวจจับความสามารถที่รองรับยังทำได้ยาก
สิ่งที่ GGUF บรรจุไว้
- GGUF คือรูปแบบไฟล์ที่ llama.cpp ใช้กับโมเดลภาษา
- จุดเด่นสำคัญของ GGUF คือการรวมองค์ประกอบหลายอย่างที่จำเป็นต่อการรันโมเดลไว้ใน ไฟล์เดียว
- คลัง safetensors ทั่วไป บน Hugging Face มักมีไฟล์ JSON ที่จำเป็นกระจายอยู่หลายแห่ง
- โมเดล Ollama ทั่วไป อยู่ในรูปแบบ OCI ที่มี layer JSON, Go template ฯลฯ
- GGUF ใส่ ข้อมูลประกอบ เหล่านี้ไว้ในไฟล์เดียว ทำให้จัดการโมเดลได้ง่ายขึ้น
เทมเพลตแชต
- โมเดลภาษาสำหรับบทสนทนาถูกฝึกด้วยลำดับโทเค็นในรูปแบบเฉพาะ ซึ่งมักดูคล้ายโครงสร้างบทสนทนา
- ตัวอย่างรูปแบบของ Gemma4 เป็นดังนี้
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
- ตัวอย่างเทมเพลตของรูปแบบ LFM2 เป็นดังนี้
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
- เทมเพลตจริงมีความซับซ้อนกว่านี้มาก เพราะรวมถึง บล็อกการให้เหตุผล, คำอธิบายเครื่องมือ, การเรียกใช้เครื่องมือและการตอบกลับ รวมถึงการเข้ารหัสข้อความมัลติมีเดียอย่างรูปภาพ เสียง และวิดีโอ
- หน้าที่นี้เป็นของ เทมเพลตแชต ซึ่งเป็นสคริปต์ที่เขียนด้วยภาษาเทมเพลต Jinja2
- ตัวอย่างคือ chat template ที่รวมอยู่ใน Gemma 4
- ในเมทาดาทาของ GGUF เทมเพลตแชตเริ่มต้นจะถูกเก็บไว้ใต้คีย์
tokenizer.chat_template
- โมเดลหนึ่งตัวอาจมีเทมเพลตแชตได้หลายแบบ
- อาจมีเทมเพลตที่รองรับการเรียกใช้เครื่องมือ และอีกเทมเพลตที่ไม่รองรับ
- โมเดลส่วนใหญ่ให้เทมเพลตแชตขนาดใหญ่เพียงอันเดียว และจะจัดการส่วนที่เกี่ยวกับการเรียกใช้เครื่องมือเมื่อมีการระบุเครื่องมือเท่านั้น
- ในบางโมเดล อาจต้องค้นหาเทมเพลตแชตเฉพาะสำหรับเครื่องมือแยกต่างหาก
- Jinja2 มีทั้งลูป เงื่อนไข การกำหนดค่า list และ dictionary จึงใกล้เคียงกับ ภาษาโปรแกรม มาก
- แอปพลิเคชัน LLM แบบสนทนาจึงต้องมี interpreter ที่รันโปรแกรมอย่างสคริปต์ Jinja ราว 250 บรรทัดที่ Gemma ให้มา ทุกครั้งที่มีข้อความใหม่ถูกเพิ่มเข้ามา
- วิธีจัดการ Jinja ก็แตกต่างกันไปตามแต่ละ implementation
- Hugging Face transformers ใช้ไลบรารี jinja2 มาตรฐานของ Python
llama-serverและllama-cliของ llama.cpp ใช้ implementation ของ Jinja ที่ทำขึ้นเอง- llama_chat_apply_template ที่เปิดผ่าน API ของ
libllamaเป็นวิธีเก่าที่ฮาร์ดโค้ดรูปแบบแชตบางแบบลงใน C++ โดยตรง - NobodyWho ใช้ minijinja ซึ่งเป็น implementation ใหม่ใน Rust โดยผู้สร้าง Jinja เอง
- ซึ่งต่างจาก minja ไลบรารี Jinja แบบมินิมอลที่ llama.cpp เคยใช้
- มีความต่างด้านประสิทธิภาพค่อนข้างมากระหว่าง implementation ของ Jinja ต่าง ๆ
- อย่างไรก็ตาม ในแอป LLM แบบรันในเครื่อง การประมวลผลเทมเพลตแชตไม่ได้เป็นคอขวดด้านประสิทธิภาพ จึงไม่ใช่ประเด็นถกเถียงใหญ่
โทเค็นพิเศษ
- โมเดลภาษาสามารถสร้างโทเค็นถัดไปต่อเนื่องจากลำดับโทเค็นขาเข้าได้เรื่อย ๆ จึงต้องมีวิธีหยุดการสร้างข้อความ
- วิธีทั่วไปคือมี โทเค็นจบการทำงาน แล้วเมื่อโมเดลส่งโทเค็นนี้ออกมา inference engine ก็จะหยุดการสร้าง
- โทเค็นจบการทำงานเป็นตัวอย่างหนึ่งของ โทเค็นพิเศษ
- โทเค็นพิเศษมักมีความหมายมากกว่าตัวอักษรที่ถูก tokenized ตามปกติ
- โดยทั่วไปไม่ควรแสดงให้ผู้ใช้เห็น แต่ก็มักมีรูปแบบข้อความที่สามารถแสดงได้
- ตัวอย่างโทเค็นพิเศษบางส่วนของ Gemma4 มีดังนี้
1/<eos>: จุดสิ้นสุดของลำดับ และเป็นโทเค็นที่โมเดลส่งออกเพื่อหยุดการสร้าง2/<bos>: จุดเริ่มต้นของลำดับ และจะถูกเติมไว้หน้าข้อมูลนำเข้า46/<|tool_call>: ระบุจุดเริ่มต้นของการเรียกใช้เครื่องมือ47/<tool_call|>: ระบุจุดสิ้นสุดของการเรียกใช้เครื่องมือ105/<|turn>: ระบุจุดเริ่มต้นของเทิร์นสนทนา106/<turn|>: ระบุจุดสิ้นสุดของเทิร์นสนทนา
ค่าตั้งค่า sampler และลำดับ
- โมเดลภาษาจะส่งออกการกระจายความน่าจะเป็นของโทเค็นถัดไป และกระบวนการเลือกโทเค็นจากการกระจายนี้เรียกว่า sampling
- วิธีที่ง่ายที่สุดคือการสุ่มเลือกจากการกระจายที่มีการถ่วงน้ำหนัก
- ในการใช้งานจริง มักได้ผลลัพธ์ที่ดีกว่าหากแปลงการกระจายความน่าจะเป็นก่อนเลือกโทเค็นจริง
- เวลาห้องวิจัยปล่อยโมเดลใหม่ ก็มักให้ ค่าตั้งค่า sampler ที่แนะนำ มาพร้อมกัน
- ผู้ใช้จึงมักต้องคัดลอกค่าเหล่านี้จากไฟล์ Markdown หรือที่อื่นมาวางเองเพื่อให้ได้คำตอบที่ดีขึ้น
- NobodyWho เคยอัปโหลดโมเดลที่คัดสรรไว้บน หน้า Hugging Face และ bundle ค่าตั้งค่า sampler ที่แนะนำด้วยรูปแบบของตัวเอง เพื่อลดการคัดลอกด้วยมือของผู้ใช้
- มันใช้งานได้ แต่โมเดลจะมีประโยชน์จริงต้องผ่านการแปลงโดยฝั่ง NobodyWho ก่อน
- ฟีเจอร์ที่เพิ่งถูกเพิ่มเข้าในรูปแบบ GGUFทำให้สามารถระบุ sampler chain ไว้ในไฟล์โมเดลได้โดยตรง
- ส่งผลให้รูปแบบเฉพาะของ NobodyWho ไม่จำเป็นอีกต่อไป ซึ่งก็เป็นผลลัพธ์ที่ต้องการอยู่แล้ว
- เว็บแอป llm-sampling ช่วยให้ดูบทบาทของขั้นตอน sampler แต่ละแบบได้อย่างรวดเร็ว
- เมื่อลากและวางแต่ละขั้นตอน จะเห็นได้ว่าการ จัดลำดับ ขั้นตอน sampling ส่งผลต่างอย่างมากต่อการกระจายสุดท้าย
- รูปแบบค่าตั้งค่า sampler จำนวนมาก เช่นไฟล์ JSON ในอิมเมจ Ollama หรือ
generation_config.jsonของ Hugging Face ไม่มีวิธีระบุลำดับของขั้นตอน sampling - มาตรฐาน GGUF สามารถระบุ ลำดับการ sampling ได้ผ่านฟิลด์
general.sampling.sequence - ถึงอย่างนั้น โมเดล GGUF จำนวนมากก็ยังละฟิลด์นี้ไว้ และอาศัยลำดับโดยนัยตามพฤติกรรมเริ่มต้นของ llama.cpp
สิ่งที่ยังขาดอยู่
- inference engine ที่ดีควรพยายามให้ อินเทอร์เฟซแบบรวมศูนย์ สำหรับโมเดลภาษาหลายแบบ
- หาก parse และใช้ข้อมูลประกอบในเมทาดาทาของ GGUF ได้ดี ก็จะลด code path เฉพาะโมเดลลงได้มาก
-
รูปแบบการเรียกใช้เครื่องมือ
- inference engine แทบทุกตัวมี code path แบบฮาร์ดโค้ดสำหรับ parse รูปแบบการเรียกใช้เครื่องมือ ที่ต่างกัน
- ตัวอย่างรูปแบบการเรียกใช้เครื่องมือของ Qwen3 เป็นดังนี้
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
- ตัวอย่างรูปแบบการเรียกใช้เครื่องมือของ Qwen3.5 เป็นดังนี้
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
- ตัวอย่างรูปแบบการเรียกใช้เครื่องมือของ Gemma4 เป็นดังนี้
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
- เมื่อมีโมเดลใหม่ออกมา inference engine หลายตัวก็ยังต้องไปเขียน parser กันเองสำหรับแต่ละตัว
- ถ้าในไฟล์โมเดลมีไวยากรณ์ (grammar) รวมอยู่ด้วย และสามารถ derive parser จากไวยากรณ์นั้นได้ ก็จะเป็นส่วนเสริมที่ยอดเยี่ยมสำหรับมาตรฐาน GGUF
- NobodyWho ยังเพิ่มขั้นตอนสำหรับสร้าง constraint grammar ให้ตรงกับเครื่องมือที่ส่งเข้ามาโดยเฉพาะ
- วิธีนี้ช่วยรับประกัน type safety ของการเรียกใช้เครื่องมือได้
- มีประโยชน์มากโดยเฉพาะกับโมเดลขนาดเล็กระดับ 1B หรือต่ำกว่า ที่อาจพลาดส่ง float มาในตำแหน่งที่ต้องเป็น integer
- แม้จะมีไวยากรณ์ที่สร้าง parser ทั่วไปสำหรับการเรียกใช้เครื่องมือได้ NobodyWho ก็ยังต้องทำฟังก์ชันสำหรับสร้างไวยากรณ์ตามเครื่องมือจริงที่ส่งเข้ามาอยู่ดี
- รูปแบบ meta grammar ที่สามารถสร้างไวยากรณ์เฉพาะสำหรับเครื่องมือหนึ่ง ๆ แล้ว derive parser จากตรงนั้นได้ ยังเป็นโจทย์ที่น่าสนใจ
-
Think token
- เป็นส่วนที่ขาดหายซึ่งเพิ่มได้ง่ายที่สุด
- คลัง upstream บน Hugging Face เริ่มมีฟิลด์
think_tokenแล้ว think_tokenมีประโยชน์มากในการแยก ช่วงความคิด ออกจากผลลัพธ์ที่สร้างขึ้น- โดยทั่วไปช่วงความคิดควรถูกตัดออก หรือแสดงผลต่างจากข้อความหลัก
- ไฟล์ GGUF ที่แปลงต่อใน downstream มักไม่ใส่ฟิลด์นี้มา
- ผลคือ inference engine ที่อิง GGUF ไม่สามารถแยกสตรีมความคิดออกจากผลลัพธ์หลักได้ หากไม่เขียนโค้ดเฉพาะสำหรับตระกูลโมเดลนั้น ๆ
- การเพิ่ม
think_tokenเข้าไปใน pipeline แปลง GGUF มาตรฐานจะช่วยแก้ปัญหานี้ได้
-
Projection model
- การโต้ตอบกับ มัลติโหมด LLM ที่ทำให้ LLM มองเห็นภาพและเสียงได้โดยตรงแทนการแปลงเป็นข้อความ ต้องมีโมเดลเสริมสำหรับประมวลผลข้อมูลนำเข้าที่ไม่ใช่ข้อความ
- โมเดลเสริมนี้เรียกว่า projection model
- ธรรมเนียมปัจจุบันคือส่งไฟล์ GGUF สองไฟล์
- ไฟล์หนึ่งสำหรับโมเดลภาษาหลัก
- อีกไฟล์เป็นโมเดลขนาดเล็กกว่าสำหรับประมวลผลภาพและเสียง
- วิธีนี้ทำลายความสะดวกแบบไฟล์เดียวของ GGUF
- หากไฟล์ GGUF เดียวสามารถ bundle ทั้งน้ำหนักและค่าตั้งค่าของ projection model ไว้ในไฟล์หลักได้ จะเป็นการปรับปรุงครั้งใหญ่
- projection model มักมีขนาดราว 1GB
- ใหญ่พอที่จะอยากหลีกเลี่ยง overhead นี้หากไม่ได้ใช้งาน
- การมี GGUF สองเวอร์ชัน คือแบบรวม projection weights และแบบไม่รวม ถือเป็นแนวทางที่สมเหตุสมผล
- แบบนี้ก็จะกลับไปสู่สถานะที่ต้องดูแลเพียง URL เดียวสำหรับดาวน์โหลด และไฟล์เดียวสำหรับแคชบนดิสก์
-
รายการความสามารถที่รองรับ
- แต่ละโมเดลรองรับความสามารถต่างกัน และจากไฟล์ GGUF เพียงอย่างเดียวมักตรวจจับความสามารถจริงที่รองรับได้ยาก
- บางโมเดลรองรับการป้อนภาพ และบางโมเดลไม่รองรับ
- ตอนนี้วิธีที่ดีที่สุดคือเดาว่าถ้ามี projection model ถูกส่งมาด้วย ก็ถือว่ารองรับภาพ
- บางโมเดลรองรับการเรียกใช้เครื่องมือแบบเนทีฟ และบางโมเดลไม่รองรับ
- ตอนนี้วิธีที่ดีที่สุดคือเช็กด้วยการจับคู่สตริงแบบบางส่วน ว่าในเทมเพลตแชตมีส่วนที่พยายาม render รายการ tool JSON schema หรือไม่
- ซึ่งชัดเจนว่าเป็นเพียงวิธีชั่วคราว
- บางโมเดลส่งบล็อกความคิดออกมา และบางโมเดลไม่ส่ง
- เนื่องจากโดยทั่วไปไม่มีแท็กความคิดอยู่ในเมทาดาทา GGUF จึงยังไม่ชัดว่าควรตรวจอย่างไรว่าโมเดลนี้คาดหวังบล็อกความคิดหรือไม่
- หากชุมชน GGUF เพิ่ม feature flags เข้าไปในไฟล์โมเดล ก็จะช่วยให้ไลบรารี inference ที่ไม่ยึดกับโมเดลใดโมเดลหนึ่งสามารถให้ข้อความผิดพลาดและคำเตือนได้สม่ำเสมอขึ้น
- เช่นสามารถแจ้งได้เหมาะสมกว่านี้เมื่อพยายามเรียกใช้เครื่องมือกับโมเดลที่ไม่รองรับการเรียกใช้เครื่องมือแบบเนทีฟ
บทสรุป
- GGUF รวมข้อมูลประกอบที่จำเป็นต่อการรันโมเดลอย่างถูกต้องไว้ใน ไฟล์เดียว ช่วยให้ไม่ต้องเพิ่ม code path เฉพาะโมเดลมากนัก
- GGUF เป็นรูปแบบที่เปิดกว้าง ขยายต่อได้ และมีชุมชนที่แข็งแรง
- หากช่วยกันทำให้มาตรฐานแข็งแรงขึ้น ก็จะรักษาประสบการณ์นักพัฒนาให้ดีไว้ได้ พร้อมกับทำให้สลับโมเดลในแอปพลิเคชันได้ง่าย
- เมทาดาทาของ GGUF มีส่วนที่เป็นประโยชน์อยู่แล้วมาก แต่ยังมีช่องให้ปรับปรุงอีก เช่น ไวยากรณ์การเรียกใช้เครื่องมือ,
think_token, การ bundle projection model และ feature flags
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
น่าเสียดายที่โมเดล projection ถูกแยกเป็นไฟล์ต่างหาก และผมเองก็อยากให้มันอยู่ใน ไฟล์เดียว มากกว่า
ไม่แน่ใจเหมือนกันว่าทำไมถึงออกมาแบบนั้น แต่ก็ค่อนข้างขัดกับแนวคิดแบบไฟล์เดียวที่มีอยู่ตอนออกแบบ GGUF
หวังว่าจะมีใครสักคนมาผลักดันให้รวมสองอย่างนี้เข้าด้วยกัน เพราะรอบนี้ดูเหมือนผมจะหลุดจากกระแสไปพอสมควร :-)
ผมชอบการตัดสินใจนั้น ดังนั้นก็คงไม่เกินเลยที่จะคิดว่ายังเปิดกว้างต่อการใส่ไฟล์ Mmproj เข้าไปใน GGUF ด้วย
ปัญหาเดียวที่นึกออกคือจะใส่มาในฟอร์แมตไหน เช่น BF16, F16 เป็นต้น
GGML และ GGUF มีความสำคัญมากต่อระบบนิเวศโอเพนซอร์สด้านแมชชีนเลิร์นนิง/AI
โปรเจกต์อย่าง llama.cpp, whisper.cpp, stable-diffusion.cpp มักใช้งานได้ดีแทบจะทันทีบนแพลตฟอร์มและฮาร์ดแวร์แบ็กเอนด์ที่หลากหลาย
คอมไพล์ ใส่โมเดล แล้วรันได้เลย จากนั้นก็จะได้ทั้งเว็บ UI และ API
> <|turn>user Hi there!<|turn>model Hi there, how can I help you todayพระเจ้า นี่มัน ฟอร์แมตที่อ่านยากกว่า XML เสียอีก
ฟอร์แมตนี้ถูกออกแบบมาเพื่อไม่ให้สับสนกับเนื้อหาจริง ซึ่งเนื้อหานั้นอาจเป็นข้อความอะไรก็ได้จากอินเทอร์เน็ต
ถ้าจะทำแบบนั้นก็ต้องใช้ฟอร์แมตที่ไม่มีใครใช้ที่ไหนเลย
ตอนนี้สิ่งที่ขาดมากที่สุดน่าจะเป็น วิธีนิยามสถาปัตยกรรมโมเดลโดยไม่ hardcode ไว้ในบิลด์ปัจจุบัน
มันไม่จำเป็นต้องมีประสิทธิภาพเทียบเท่าแบบ 1:1 กับโมเดลที่รองรับเต็มรูปแบบก็ได้
การมีซัพพอร์ตที่ถูกต้องและผ่านการตรวจสอบจากผู้พัฒนาตั้งแต่วันแรกที่ออก มีผลอย่างมากว่าคนจะรู้สึกว่าโมเดลนั้นยอดเยี่ยมหรือแย่มาก ตัวอย่างล่าสุดคือการออกของ Gemma และ Qwen
ยังไม่แน่ใจว่าคำตอบคืออะไร แต่อาจเป็นการเขียน DSL เพื่ออธิบายกราฟของโมเดลแล้วใส่ลงใน GGUF
อีกทางเลือกคืออ่าน PyTorch module จากโมเดลที่ปล่อยอย่างเป็นทางการ แล้วแปลงเป็นโอเปอเรชันของ GGML ไม่ทางใดก็ทางหนึ่ง
ผมอยากใส่มันไว้ตั้งแต่เวอร์ชันแรก แต่ตอนนั้นให้ความสำคัญกับการออกสเปกขั้นต่ำที่ใช้งานได้จริงก่อน
ตอนนี้ก็ยังอยากเห็นอยู่ แต่ต้องการคนที่ผลักดันเรื่องนี้และเข้าใจสถานะปัจจุบันของ GGML IR เป็นอย่างดี
จากนั้นก็ค่อยเปิดเผยอินเทอร์เฟซกลางที่รับพารามิเตอร์ร่วมกัน และปล่อยให้พารามิเตอร์ custom เพิ่มเติมอยู่ในรูปส่วนขยายแบบ Wayland
แบบนั้นก็น่าจะรองรับได้ทั้งทรานส์ฟอร์เมอร์สาย LLaMa, โครงข่ายประสาทแบบวนซ้ำอย่าง RWKV, รวมถึงโมเดลมัลติโหมดัล
ผมไม่รู้ว่าทำจริงจะออกมาอย่างไร แต่ฟังดูเป็นไอเดียที่เจ๋งมาก เพียงแต่ก็อดกังวลไม่ได้ว่าถ้า computation graph ถูกฝังตายไว้ในไฟล์โมเดล การปรับปรุงสถาปัตยกรรมหรือการเพิ่มประสิทธิภาพที่ไม่ต้องเปลี่ยนน้ำหนัก อาจนำไปใช้กับไฟล์เดิมไม่ได้หากไม่แปลงก่อน
> จุดที่ดีมากจริง ๆ ของ GGUF คือมันเป็นไฟล์เดียว เมื่อเทียบกับ safetensors repo ทั่วไปบน Hugging Face ที่มีไฟล์ JSON ที่ต้องใช้กระจัดกระจายอยู่เต็มไปหมด [...]น่าสนใจตรงที่สำหรับผม โมเดล AI นั้น “เป็น ไฟล์เดียว มาตลอด” เพราะในฝั่งการสร้างภาพแบบโลคัล มาตรฐานมันเป็นแบบนั้น
ไฟล์ safetensors เองก็ยัดของสารพัดอย่างไว้ข้างในได้อยู่แล้ว ดังนั้นไม่ได้จำเป็นต้องมี GGUF เพื่อทำเรื่องนี้เสมอไป
เพียงแต่ text encoder ของโมเดลสมัยใหม่เองก็มักเป็น language model ขนาดหลายกิกะไบต์ ดังนั้นจึงไม่มีใครใส่สำเนาซ้ำไว้ในทุก checkpoint หรอก
โมเดลภาพส่วนใหญ่เป็นไฟล์เดียวอยู่แล้วหรือทุกวันนี้ก็ยังเป็น แต่ safetensors ของ LLM อย่างน้อยในตอนนั้นยังไม่ใช่ และผมอยากบังคับเรื่องนี้ในระดับโครงสร้าง
อีกอย่างคือผมไม่อยากให้ตัวรัน เช่น llama.cpp ต้องมี JSON reader ซึ่งแนวทาง ST น่าจะบังคับให้ต้องมี
ถ้าจำไม่ผิด ปัญหาใหญ่กว่านั้นคือตอนนั้น ST ยังไม่รองรับฟอร์แมต quantization ใหม่ของ GGML และการมีฟอร์แมตไฟล์ของตัวเองก็ทำให้ได้ความยืดหยุ่นที่ ST ให้ได้ยาก
ในการรันสถาปัตยกรรมจากน้ำหนักจริง ๆ มันไม่ได้ใช้แค่ไฟล์น้ำหนักไฟล์เดียว แต่ยังต้องมี encoder และ decoder หลายตัวรวมถึงองค์ประกอบอื่น ๆ
เครื่องมือที่ใช้สามารถซ่อนสิ่งเหล่านั้นจากผู้ใช้ได้ แต่ลึกลงไปข้างใต้ก็ยังมีอยู่เหมือนเดิม
เรื่อง
llama_chat_apply_templateที่ค่อนข้างแปลก ซึ่งถูกเปิดผ่าน API ของlibllamaและ hardcode ฟอร์แมตแชตบางแบบไว้ใน C++ นั่นแหละ ในฐานะคนที่กำลังลองทำแอป inference บนเดสก์ท็อปด้วย FLTK[0] ผมอยากให้มันใช้ Jinja2 template parser ตัวจริงที่ llama.cpp ใช้อยู่หรือไม่ก็อยากให้มี C function ตัวอื่นที่ทำหน้าที่แบบนั้น การจะ parse ให้ถูกต้อง ดูเหมือนต้องส่งข้อมูลหลายอย่างเข้าไป เช่น เพื่อให้ template รู้ว่ามีการเรียกใช้เครื่องมือหรือไม่
ตอนนี้ผมยังใช้ฟังก์ชันชั่วคราวตัวนี้อยู่ แต่สุดท้ายก็น่าจะต้องใช้ Jinja2 interpreter โดยตรง หรือไม่ก็ดึงโค้ดจาก llama.cpp มาแปะเอง
ถึงอย่างนั้นแนวทางแบบ all-in-one ของ GGUF ก็สะดวกมาก และผมก็เห็นด้วยว่าการที่ projection model เป็นไฟล์แยกมันให้ความรู้สึกแปลก ๆ
ตอนที่ผมโหลดโมเดลที่รองรับวิชันเป็นครั้งแรก ผมดาวน์โหลดแค่ GGUF ที่ดูเหมาะสม แต่ llama.cpp กลับบอกว่าใช้โมเดลนั้นไม่ได้ ทำให้ผมเพิ่งมารู้ทีหลังอีกนานว่าต้องมีไฟล์เพิ่ม
ตอนนั้นความคิดที่ผุดขึ้นมาคือ “GGUF ไม่ใช่ฟอร์แมตที่ใส่ทุกอย่างไว้หมดหรอกเหรอ?” แบบตรงตัวเลย :-P
[0] https://i.imgur.com/GiTBE1j.png
ผมใช้ฟอร์แมตแบบ safetensors + ไฟล์เมตาดาตา คล้ายกับรีโพของ Hugging Face มาโดยตลอด
มันไม่ได้สร้างความลำบากใหญ่โตอะไร แต่การที่ GGUF มีฟอร์แมตที่กะทัดรัดกว่าและมีซัพพอร์ตที่ดีกว่าก็ดูน่าสนใจ
การดูว่ายังมีอะไรที่ GGUF ไม่มี กลับทำให้ผมเข้าใจ GGUF มากขึ้น
ฟอร์แมตการเรียกใช้เครื่องมือ นั้นเป็นธรรมชาติมาก และดูเหมือนจะเป็นหมุดหมายสำคัญของการก้าวจาก LLM ไปสู่ agent
ไม่นานมานี้ผมไปโหลด 7B Mistral ของ TheBloke มาลองทดสอบ และผมมี 4070 อยู่
น่าจะลอง Gemma 4 e4b ดู ขนาดใกล้กับ Mistral 7B และน่าจะรันบน 4070 ได้ดี
ชื่อ “E4B” อาจทำให้เข้าใจผิดได้นิดหน่อย
บน 4070 ที่มี 12GB คุณสามารถรัน Qwen 3.5 9B q4km หรือ Qwen 3.6 35B ได้ โดยตัวหลังฉลาดกว่ามากแต่ก็ช้ากว่ามากเพราะต้อง offload หน่วยความจำ
ลองทั้งคู่ใน LM Studio ได้เลย ความสามารถดีจนน่าทึ่งจริง ๆ
ผมชอบ TheBloke มาก และยังอยากให้เขาทำโมเดลต่อไป