3 คะแนน โดย GN⁺ 3 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • GGUF คือรูปแบบไฟล์โมเดลภาษาที่ llama.cpp ใช้งาน โดยรวมเมทาดาทาที่จำเป็นต่อการรันไว้ใน ไฟล์เดียว เพื่อให้การแจกจ่ายและการโหลดโมเดลง่ายขึ้น
  • เทมเพลตแชต เป็นสคริปต์ Jinja2 ที่จัดการรูปแบบบทสนทนา การเรียกใช้เครื่องมือ และการเข้ารหัสข้อความมัลติมีเดีย แต่พฤติกรรมยังต่างกันไปตามแต่ละ implementation
  • GGUF สามารถเก็บ โทเค็นพิเศษ เช่นโทเค็นจบการทำงาน และค่าตั้งค่า sampler ที่แนะนำได้ และล่าสุดยังสามารถระบุ ลำดับของ sampler chain ได้แล้ว
  • ตอนนี้ รูปแบบการเรียกใช้เครื่องมือ ยังแตกต่างกันไปตามแต่ละโมเดล จึงยังต้องมีการฮาร์ดโค้ดตาม inference engine และการสร้าง parser จากไวยากรณ์ยังเป็นตัวเลือกสำหรับการปรับปรุงมาตรฐานในอนาคต
  • ยังขาด think_token, การ bundle projection model และ feature flags ทำให้การแยกช่วงความคิด การจัดองค์ประกอบแบบมัลติโหมด และการตรวจจับความสามารถที่รองรับยังทำได้ยาก

สิ่งที่ GGUF บรรจุไว้

  • GGUF คือรูปแบบไฟล์ที่ llama.cpp ใช้กับโมเดลภาษา
  • จุดเด่นสำคัญของ GGUF คือการรวมองค์ประกอบหลายอย่างที่จำเป็นต่อการรันโมเดลไว้ใน ไฟล์เดียว
  • 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
  • โมเดลหนึ่งตัวอาจมีเทมเพลตแชตได้หลายแบบ
    • อาจมีเทมเพลตที่รองรับการเรียกใช้เครื่องมือ และอีกเทมเพลตที่ไม่รองรับ
    • โมเดลส่วนใหญ่ให้เทมเพลตแชตขนาดใหญ่เพียงอันเดียว และจะจัดการส่วนที่เกี่ยวกับการเรียกใช้เครื่องมือเมื่อมีการระบุเครื่องมือเท่านั้น
    • ในบางโมเดล อาจต้องค้นหาเทมเพลตแชตเฉพาะสำหรับเครื่องมือแยกต่างหาก
  • 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 ความคิดเห็น

 
GN⁺ 3 시간 전
ความคิดเห็นจาก Hacker News
  • น่าเสียดายที่โมเดล projection ถูกแยกเป็นไฟล์ต่างหาก และผมเองก็อยากให้มันอยู่ใน ไฟล์เดียว มากกว่า
    ไม่แน่ใจเหมือนกันว่าทำไมถึงออกมาแบบนั้น แต่ก็ค่อนข้างขัดกับแนวคิดแบบไฟล์เดียวที่มีอยู่ตอนออกแบบ GGUF
    หวังว่าจะมีใครสักคนมาผลักดันให้รวมสองอย่างนี้เข้าด้วยกัน เพราะรอบนี้ดูเหมือนผมจะหลุดจากกระแสไปพอสมควร :-)

    • ตอนนี้เห็นว่า กำลังพัฒนา MTP support อยู่ ระหว่างการคุยน่าจะเคยมีแนวคิดให้แยกโมเดล MTP ออกจาก GGUF หลักเหมือน Mmproj แต่ถูกปฏิเสธไป
      ผมชอบการตัดสินใจนั้น ดังนั้นก็คงไม่เกินเลยที่จะคิดว่ายังเปิดกว้างต่อการใส่ไฟล์ Mmproj เข้าไปใน GGUF ด้วย
      ปัญหาเดียวที่นึกออกคือจะใส่มาในฟอร์แมตไหน เช่น BF16, F16 เป็นต้น
  • GGML และ GGUF มีความสำคัญมากต่อระบบนิเวศโอเพนซอร์สด้านแมชชีนเลิร์นนิง/AI
    โปรเจกต์อย่าง llama.cpp, whisper.cpp, stable-diffusion.cpp มักใช้งานได้ดีแทบจะทันทีบนแพลตฟอร์มและฮาร์ดแวร์แบ็กเอนด์ที่หลากหลาย

    • llama.cpp จะว่าไปก็เป็นผลงานฝั่ง Meta และผมไม่ชอบ Meta เอามาก ๆ แต่ก็ยอมรับว่าเมื่อเทียบกับอย่างอื่นแล้วมันง่ายที่สุด
      คอมไพล์ ใส่โมเดล แล้วรันได้เลย จากนั้นก็จะได้ทั้งเว็บ 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 ไม่ทางใดก็ทางหนึ่ง

    • ในสเปกของ GGUF มีการเว้นที่ไว้โดยตั้งใจสำหรับ computation graph และผมหวังว่าจะมีใครมารับช่วงต่อ
      ผมอยากใส่มันไว้ตั้งแต่เวอร์ชันแรก แต่ตอนนั้นให้ความสำคัญกับการออกสเปกขั้นต่ำที่ใช้งานได้จริงก่อน
      ตอนนี้ก็ยังอยากเห็นอยู่ แต่ต้องการคนที่ผลักดันเรื่องนี้และเข้าใจสถานะปัจจุบันของ GGML IR เป็นอย่างดี
    • ดูเหมือนว่าจะฝัง computation graph ลงไปในไฟล์น้ำหนักได้แบบเดียวกับ ONNX
      จากนั้นก็ค่อยเปิดเผยอินเทอร์เฟซกลางที่รับพารามิเตอร์ร่วมกัน และปล่อยให้พารามิเตอร์ 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 ให้ได้ยาก
    • คำพูดที่ว่า “ในฝั่งการสร้างภาพแบบโลคัล โมเดล AI เป็นไฟล์เดียวมาตลอด” ก็ไม่จริงแม้แต่ในวงการนั้นเอง
      ในการรันสถาปัตยกรรมจากน้ำหนักจริง ๆ มันไม่ได้ใช้แค่ไฟล์น้ำหนักไฟล์เดียว แต่ยังต้องมี 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 อยู่

    • ผมชอบ Mistral แต่โมเดลนั้นไม่ใช่ตัวที่ดีที่สุดแล้ว
      น่าจะลอง Gemma 4 e4b ดู ขนาดใกล้กับ Mistral 7B และน่าจะรันบน 4070 ได้ดี
      ชื่อ “E4B” อาจทำให้เข้าใจผิดได้นิดหน่อย
    • 7B Mistral ค่อนข้างเก่าแล้ว
      บน 4070 ที่มี 12GB คุณสามารถรัน Qwen 3.5 9B q4km หรือ Qwen 3.6 35B ได้ โดยตัวหลังฉลาดกว่ามากแต่ก็ช้ากว่ามากเพราะต้อง offload หน่วยความจำ
      ลองทั้งคู่ใน LM Studio ได้เลย ความสามารถดีจนน่าทึ่งจริง ๆ
    • ผมยืนยันได้เลยว่ามันทำงานได้ดีและเร็วมากแม้บน 2070
      ผมชอบ TheBloke มาก และยังอยากให้เขาทำโมเดลต่อไป