1 คะแนน โดย GN⁺ 2024-10-12 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • lm.rs เป็นโปรเจกต์สำหรับรันการอนุมานโมเดลภาษาบนเครื่องแบบโลคัลบน CPU ด้วย Rust โดยมุ่งเป็นการใช้งานโค้ดขนาดเล็กที่ทำการอนุมานทั้งหมดโดยไม่ใช้ไลบรารี ML
  • ได้แรงบันดาลใจจาก llama2.c และ llm.c ของ Karpathy ช่วงแรกสนับสนุนเฉพาะ Google Gemma 2 แต่ภายหลังขยายไปถึงการรองรับ Llama 3.2 และอินพุตรูปภาพของ PHI-3.5
  • การเปลี่ยนแปลงล่าสุดมีการใช้งาน batch processing ทำให้ความเร็วในการเข้ารหัสภาพเพิ่มขึ้นสูงสุดประมาณ 3 เท่า และ Llama 3.2 1B รันได้ที่ 50 tok/s บนเครื่อง 16 คอร์ของผู้เขียน
  • โมเดลที่เตรียมไว้สามารถดาวน์โหลดได้จาก Hugging Face โดย README แนะนำให้ใช้ Q8_0 และระบุว่าการ quantization แบบ Q4_0 ยังอยู่ระหว่างปรับปรุง
  • ผู้ใช้สามารถดาวน์โหลดโมเดลรูปแบบ LMRS และ tokenizer มาบิลด์ได้ทันที หรือแปลงไฟล์โมเดลต้นฉบับจาก Hugging Face ด้วย export.py และ tokenizer.py เพื่อรันได้

เป้าหมายของ lm.rs

  • lm.rs เป็น implementation สำหรับการอนุมานโมเดลภาษาแบบโลคัลบน CPU ที่เขียนด้วย Rust
  • เป้าหมายคือการใช้งานโค้ดขนาดเล็กที่ทำ การอนุมานทั้งหมด ของโมเดลภาษาบน CPU โดยไม่ใช้ไลบรารี ML
  • ได้แรงบันดาลใจจาก llama2.c และ llm.c ของ Karpathy
  • README ระบุว่าโค้ดปัจจุบัน “ไม่ได้มินิมัลขนาดนั้น” และบางส่วนยังมีพื้นที่ให้ปรับแต่งและปรับปรุงได้
  • โปรเจกต์นี้ยังเป็นโอกาสให้ผู้เขียนได้ลองใช้ Rust เป็นครั้งแรกด้วย

โมเดลที่รองรับและการขยายสู่มัลติโมดัล

  • ช่วงแรกสนับสนุนเฉพาะโมเดล Google Gemma 2 แต่ต่อมาเพิ่มการรองรับโมเดล Llama 3.2
  • ล่าสุดมีการเพิ่มตัวเลือกการใช้รูปภาพผ่าน PHI-3.5
  • รายการรองรับที่เน้นในปัจจุบัน
    • รองรับมัลติโมดัลผ่านโมเดล PHI-3.5-vision
    • รองรับโมเดลข้อความอย่างเดียว PHI-3.5-mini
  • ทรัพยากรที่เกี่ยวข้อง

ประสิทธิภาพและโมเดลที่เตรียมไว้

  • ข่าวล่าสุดคือมีการใช้งาน batch processing ทำให้ความเร็วในการเข้ารหัสภาพดีขึ้นสูงสุดประมาณ 3 เท่า
  • Llama 3.2 1B รันได้ที่ 50 tok/s บน เครื่อง 16 คอร์ ของผู้เขียน
  • สามารถดาวน์โหลดโมเดลและ tokenizer ที่เตรียมไว้ได้จาก Hugging Face
  • การวัดความเร็วทำบน AMD Epyc 16 คอร์
  • README แนะนำให้ใช้ Q8_0 และระบุว่าการ quantization แบบ Q4_0 ยังอยู่ระหว่างปรับปรุง
  • ตารางโมเดลที่เตรียมไว้

ขั้นตอนการแปลงโมเดล

  • หากดาวน์โหลดโมเดลที่ quantize แล้วและ tokenizer ที่เตรียมไว้จาก Hugging Face ก็สามารถข้ามขั้นตอนการแปลงได้
  • หากต้องการแปลงโมเดลที่ Google หรือ Meta เผยแพร่บน Hugging Face ด้วยตัวเอง ต้องติดตั้ง dependency เพิ่มเติมของ Python
pip install -r requirements.txt
  • ใช้ไฟล์ .safetensors และ config.json ที่ดาวน์โหลดจากหน้าโมเดลต้นฉบับ
  • สำหรับโมเดลมัลติโมดัลอย่าง PHI3.5 Vision ต้องใช้ไฟล์ CLIP config ด้วย
  • export.py แปลงน้ำหนัก bfloat16 เป็น รูปแบบ LMRS
python export.py --files [ordered .safetensor files] --config [model config.json] --save-path [name and path to save] --type [model type (GEMMA/LLAMA/PHI)]
  • หากต้องการส่งออกเวอร์ชัน quantize ให้ใช้แฟล็ก --quantize และ --quantize-type
  • ขนาดโมเดลที่ quantize แบบ int8 อาจลดจากประมาณ 9.8G เหลือประมาณ 2.5G ขึ้นอยู่กับขนาดกลุ่ม
  • โมเดลมัลติโมดัลต้องใส่อาร์กิวเมนต์ --vision-config
  • tokenizer.py แปลงโมเดล tokenizer เป็นรูปแบบ tokenizer ของ LMRS
python tokenizer.py --model-id [huggingface model_id] --tokenizer-type [type of the tokenizer (GEMMA/LLAMA/PHI)]

การบิลด์และการรัน

  • โค้ด Rust คอมไพล์ด้วย cargo และ README ระบุให้ส่งแฟล็ก target-cpu
RUSTFLAGS="-C target-cpu=native" cargo build --release --bin chat
  • หากต้องการเปิดใช้ฟีเจอร์มัลติโมดัล ให้เพิ่มอาร์กิวเมนต์ --features multimodal
  • การรันพื้นฐานทำโดยระบุไฟล์น้ำหนักโมเดล
./target/release/chat --model [model weights file]
  • สามารถใช้อาร์กิวเมนต์เพิ่มเติมอย่าง tokenizer, temperature, top-p, show-metrics ได้
  • ตรวจสอบอาร์กิวเมนต์ที่ใช้ได้ด้วย --help
  • สำหรับโมเดลมัลติโมดัล ให้ระบุพาธรูปภาพด้วยอาร์กิวเมนต์ --image
  • เมื่อใช้ PHI3.5-vision README แนะนำให้ตั้ง temperature เป็น 0

การรันแบ็กเอนด์ WebUI

  • หากต้องการรันแบ็กเอนด์สำหรับ WebUI ให้คอมไพล์ด้วยฟีเจอร์ backend
RUSTFLAGS="-C target-cpu=native" cargo build --release --features backend --bin backend
  • แบ็กเอนด์มัลติโมดัลให้เปิดใช้ฟีเจอร์ backend-multimodal
  • รันแบ็กเอนด์โดยระบุไฟล์น้ำหนักโมเดล
./target/release/backend --model [model weights file]
  • สามารถเปลี่ยน IP และพอร์ตได้ด้วย --ip และ --port
  • สามารถใช้แฟล็กเพิ่มเติมอย่าง temperature ได้เช่นกัน
  • สำหรับความเข้ากันได้กับมัลติโมดัล ให้ใช้แฟล็ก --multimodal
  • หลังรันแล้วสามารถเชื่อมต่อผ่านเว็บอินเทอร์เฟซได้

สถานะ TODO และไลเซนส์

  • รายการที่เสร็จแล้ว
    • เพิ่มวิธี sampling อื่น ๆ
    • ในรายการทดสอบโมเดล 9B และ 27B ทดสอบ 9B เสร็จแล้ว ส่วน 27B ระบุว่าน่าจะช้าเกินไป
    • ทำ parallelization ให้ลูป multi-head attention
    • เพิ่มตัวชี้วัดประสิทธิภาพ
    • รองรับ int8, int4 quantization
  • รายการที่เหลือ
    • ฟีเจอร์สำหรับให้ system prompt
  • ไลเซนส์คือ MIT

1 ความคิดเห็น

 
GN⁺ 2024-10-12
ความคิดเห็นจาก Hacker News
  • ลองรัน llama3.2-1b-it-q80.lmrs ขนาด 1.2GB บน MacBook M2 64GB แล้วรู้สึกว่าค่อนข้างเร็ว และจาก Activity Monitor เห็นว่าใช้ CPU 1000% บน 13 เธรด
    โคลน lm.rs ไว้ที่ /tmp แล้วบิลด์ด้วย RUSTFLAGS="-C target-cpu=native" cargo build --release --bin chat จากนั้นดาวน์โหลด tokenizer.bin และ llama3.2-1b-it-q80.lmrs จาก Hugging Face แล้วรันด้วย ./target/release/chat --model llama3.2-1b-it-q80.lmrs

    • มีคนถามว่าลองรันด้วย ./target/release/chat --model llama3.2-1b-it-q80.lmrs --show-metrics เพื่อดูว่าได้ จำนวนโทเคนต่อวินาที เท่าไรได้ไหม
    • ใส่พรอมป์ต์สนทนาภาษาฝรั่งเศสแบบง่าย ๆ เข้าไป ตอนแรกเหมือนจะตอบได้ แต่ไม่นานก็พังกลายเป็น เอาต์พุตขยะที่อ่านไม่รู้เรื่องแบบไม่มีที่สิ้นสุด
      เหลือไว้แค่บางส่วนเพราะเรื่องฟอร์แมต แต่ลักษณะคือเป็นสายคำสุ่มยาว ๆ ต่อเนื่องไปเรื่อย ๆ
    • สงสัยว่าควรทำความเข้าใจอย่างไรว่าเมื่อเทียบกับ ChatGPT รุ่นล่าสุดแล้ว สิ่งนี้ ฉลาดแค่ไหน
  • บทความเขียนได้ดีมาก และน่าจะนำซอร์สโค้ดบางส่วนไปใช้ในชั้นเรียนเวลาอธิบายว่า Transformer ทำงานจริงอย่างไรได้
    โค้ดเป็นรูปธรรมและละเอียดกว่าภาพแสดง attention head มาก อย่างไรก็ตาม ถ้าไลบรารีพิมพ์ออก stdout โดยตรง อาจทำให้เอาต์พุตของแอปพลิเคชันประเภทที่ให้ตัวแก้ไขข้อความตรวจสไตล์เสียได้ ดังนั้นน่าจะดีกว่าถ้าเขียนลงบัฟเฟอร์สตริงของอินสแตนซ์ล็อกที่เชื่อมกับอ็อบเจกต์ lm.rs
    อีกอย่าง เห็นส่วนที่ใช้ unsafe ใน model reader เพื่อบังคับการจัดแนวข้อมูล เลยสงสัยว่าทำได้โดย ไม่ใช้ unsafe แบบไม่ฝืนเกินไปหรือไม่

    • เรียกใช้ คอลแบ็กของผู้ใช้ น่าจะดีกว่าบัฟเฟอร์สตริง
      แบบนั้นจะจัดการต่อได้ เช่น แสดงล็อกใน GUI
  • ทำเครื่องมือ Rust สำหรับโหลดโมเดลและงาน LLM หลายอย่างไว้พอสมควร
    มีฟังก์ชันอย่างการเลือกโมเดลควอนไทซ์ที่ใหญ่ที่สุดโดยอัตโนมัติตามหน่วยความจำที่มีอยู่, ดึง tokenizer จาก gguf, หรือใส่พรอมป์ต์ เป็นต้น ถ้าใช้สิ่งนี้น่าจะตัด dependency ฝั่ง Python บางส่วนออกได้
    ตอนนี้รองรับ llama.cpp แต่ตัวนี้ก็น่าสนใจมาก สงสัยว่ามีแผนรองรับ grammar constraints ด้วยหรือไม่
    https://github.com/ShelbyJenkins/llm_client

  • คำว่า no dependency ในชื่อเรื่องยังไม่ค่อยชัดเจน
    ตอนเห็นครั้งแรกคิดว่าอาจหมายถึง no_std แต่จริง ๆ แล้วไม่ใช่ no_std และดูเหมือนมี dependency อยู่หลายตัว บางทีอาจหมายถึงเป็น dependency ฝั่ง Rust ทั้งหมดก็ได้

    • การตั้งชื่อเรื่องเป็นเรื่องยาก สิ่งที่อยากสื่อคือไม่มี dependency ด้านดีปเลิร์นนิงอย่าง PyTorch, CUDA, ONNX และตรรกะทั้งหมดรวมอยู่ในตัวเอง
      ถ้าพูดอย่างโปร่งใส มี dependency พื้นฐานของ Rust อยู่ 5 ตัว โดยในนั้น chrono กับ clap ควรจัดการด้วย feature flag สำหรับฟังก์ชันแชต ส่วนอีก 3 ตัวเป็น utility crate เพื่อดึงประสิทธิภาพฮาร์ดแวร์เพิ่มเล็กน้อย ได้แก่ rayon สำหรับทำ parallelization ให้ง่ายขึ้น, wide สำหรับช่วย SIMD และ memmap2 สำหรับ memory mapping ไฟล์โมเดล
    • ดูจาก README แล้วใน requirements.txt เหมือนต้องใช้ PyTorch และ dependency ของ Python หลายตัว และจุดเดียวบนเพจที่เห็นคำว่า “dependency” ก็อยู่ตรงนั้น จึงทำให้ถ้อยคำในชื่อเรื่องค่อนข้างสับสน
      ตัวโปรเจกต์เองดูเหมือนใช้คำโปรยว่า “Minimal LLM inference in Rust” เฉย ๆ จากประวัติ Git บัญชีที่โพสต์บทความนี้เป็น contributor แต่ไม่น่าจะใช่ผู้เขียนหลัก ถ้าอธิบายได้ว่า zero dependencies หมายถึงอะไรกันแน่ก็น่าจะช่วยได้
    • เดิมทีถ้าตั้งชื่อว่า “ไม่มี dependency ด้านฮาร์ดแวร์” หรือ “ไม่มี dependency กับ GPU” อะไรทำนองนั้น ก็คงสมเหตุสมผลกว่า
      น่าเสียดายที่ HN มักลบคำออกจากชื่อเรื่องโดยแทบไม่มีเหตุผลหรือความโปร่งใส
    • เริ่มรู้สึกว่า cargo ของ Rust ตอนนี้แทบจะเหมือน npm แล้วหรือเปล่า
      มี dependency 16 ตัว แล้วจะบอกว่า ไม่มี dependency ได้อย่างไรไม่รู้
  • เคยทำของคล้าย ๆ กันมาก่อน แต่เมื่อเทียบกับโค้ด C/C++ ที่รันบน CPU แล้ว ประสิทธิภาพยังไม่น่าพอใจ
    นั่นก็หมายความว่าตอนนั้นยังไม่รู้วิธีทำให้ Rust เร็วอย่างถูกต้องด้วย ถ้ามี benchmark ของ implementation ต่าง ๆ ใน Rust ก็คงดี
    implementation สำหรับ LLM inference อาจกลายเป็น “Hello, world!” แบบใหม่สำหรับโปรแกรมเมอร์สายจริงจังก็ได้
    https://github.com/gip/yllama.rs

    • ฉันเองก็เคยทำของจากประสบการณ์ “Hello, world” คล้าย ๆ กัน
      https://github.com/crabml/crabml
      ใช้คำสั่ง SIMD บางส่วนโดยตรง และดูเหมือนประสิทธิภาพจะทำได้ใกล้เคียงกับ llama.cpp แก่นสำคัญน่าจะอยู่ที่ การใช้ SIMD ในการคูณเมทริกซ์แบบควอนไทซ์ และการใช้ลูป busy-wait แทน condition variable ตอนแบ่งงานระหว่างเธรด
      อย่างไรก็ตาม ไม่มีเวลาทำงานต่อกับการ inference โมเดลแบบควอนไทซ์บน GPU ด้วย Vulkan จึงไม่ได้อัปเดตมาสักพักแล้ว
  • น่าสนใจที่ใช้ Dioxus อยู่แล้ว และสงสัยว่าในโรดแมปจะมี WASM รวมอยู่ด้วยได้ไหม
    หากสามารถรัน LLM เบา ๆ อย่าง RWKV ในเบราว์เซอร์ได้ เบราว์เซอร์ก็อาจเปิดหมวดหมู่ฟีเจอร์ใหม่ ๆ ได้โดยไม่ต้องเรียก SaaS API

    • เคยลองแตะด้านนี้มาบ้างเล็กน้อย
      https://github.com/maedoc/rwkv.js
      ใช้ Rwkv.cpp ที่คอมไพล์ด้วย Emscripten แต่ส่วน tokenizer ยังแก้ได้ไม่เรียบร้อยนัก ถึงอย่างนั้น 1.6B RWKV6 ก็น่าจะใช้งานได้เพียงพอสำหรับเบราว์เซอร์แบบออฟไลน์อย่างเดียว
      สำหรับแชตทั่วไปอาจยังความสามารถไม่พอ แต่สำหรับงานอย่าง RAG อาจเพียงพอทีเดียว
    • ตัวไลบรารีเองน่าจะ คอมไพล์เป็น WASM ได้ด้วยการเปลี่ยนแปลงน้อยมาก
      dependency จำเป็นอย่าง rayon และ wide รองรับ WASM โดยตรง และถ้าเปลี่ยน type Mmap ใน transformer.rs เป็น &[u8] ก็ถอด memmap2 ออกได้
      อย่างไรก็ตาม RWKV มีโครงสร้างที่แตกต่างไปโดยสิ้นเชิง จึงต้อง implement ใหม่ทั้งหมด และดูมีโอกาสต่ำมากที่จะเข้าไปอยู่ในโรดแมป
  • สงสัยว่า implementation เหล่านี้ทั้งหมด จำกัดเฉพาะ CPU หรือไม่
    กำลังถามว่าถ้ามี GPU ดี ๆ ก็ควรมองหาทางเลือกอื่นใช่ไหม

    • ใช่ โปรเจกต์นี้ ทำงานบน CPU จึงไม่ได้ใช้ GPU ในการคำนวณ
      หากอยากลองใช้เฟรมเวิร์ก Rust ที่รองรับ GPU ก็น่าดู Candle https://github.com/huggingface/candle/tree/main
    • ทั้งหมด implement บน CPU และอย่างน้อยตอนนี้ก็ ไม่มี GPU acceleration เลย
      ถ้าจุดประสงค์คือจะเอาไปรันจริง แม้จะใช้แค่ CPU ก็น่าจะใช้ทางเลือกอื่นคือ llama.cpp ดีกว่า โปรเจกต์นี้ใกล้เคียงกับสื่อการเรียนรู้ที่แสดงให้เห็นว่าภายในทำงานอย่างไรเมื่อเอาชั้นความซับซ้อนของ ecosystem ออกไป
      LLM อาจดูเหมือนเวทมนตร์ในแง่ผลลัพธ์ แต่จากมุมมองโค้ดแล้วค่อนข้างเรียบง่าย
    • อาจต่างกันได้ 10–20 เท่า แล้วแต่ GPU
      ฝั่ง Rust มี wrapper ของ llama.cpp อย่าง llm_client ของฉัน และมีโปรเจกต์ที่ใช้ Candle อย่าง mistral.rs กับ Kalosm
      โปรเจกต์ของฉันก็พยายามจะให้ implementation ของ mistral.rs ด้วย แต่ยังย้ายออกจาก llama.cpp ได้ไม่สมบูรณ์ การ implement ด้วย Rust ล้วนมีข้อดีมาก เช่น ทำให้เวลาติดตั้งเร็วขึ้น ตอนนี้ crate ของฉันยังต้อง clone แล้ว build แม้จะทำ automation บน macOS, Windows, Linux ไว้แล้ว แต่ก็ทำให้เวลา build เพิ่มขึ้นราว 1 นาที
    • CPU ก็ใช่ แต่สิ่งที่สำคัญกว่าคือ memory bandwidth
      เช่น RTX 3090 มี memory bandwidth เกือบ 1TB/s หากจะให้ทันระดับนี้ ต้องใช้ DDR5 ระดับ proof-of-concept ที่เร็วที่สุดในโลกอย่างน้อย 12 channel
      ถ้ามี GPU แยก การใช้ implementation ที่ใช้ประโยชน์จากมันได้ถือเป็นคนละโลกเลย เหตุผลที่ตัวเลข inference ของ LLM บน Apple Silicon น่าประทับใจ ก็เพราะมีสถาปัตยกรรมหน่วยความจำ bandwidth สูงแบบรวม CPU-GPU และเท่าที่จำได้อยู่ที่ประมาณ 400GB/s
    • แล้วแต่กรณี โมเดลที่ดีมักใหญ่และมี ความต้องการหน่วยความจำ สูง
      แม้แต่ 4090 ก็ไม่ได้มีหน่วยความจำเยอะนักตามมาตรฐาน LLM GPU น่าจะเร็วกว่า แต่มีโอกาสสูงที่จะโหลดโมเดลใหญ่ขึ้นไปไม่ได้
  • สงสัยว่าสิ่งนี้มีคุณค่าอะไรเมื่อเทียบกับ llama.cpp

    • อาจ ผสานรวม กับโปรเจกต์ Rust อื่น ๆ ได้ง่ายกว่า
    • ฟีเจอร์น้อยกว่า ทำให้ codebase สะอาดกว่า
  • เจ๋งมาก และยินดีด้วยที่ทำไลบรารี Rust ตัวแรก แต่สำหรับการใช้งาน local แบบจริงจัง การรองรับ Metal/CUDA เป็นสิ่งจำเป็น

    • ถ้าใช้ CUDA ก็จะขัดกับเป้าหมายของโปรเจกต์นี้ จึงไม่ตรงตั้งแต่ต้น
      อย่างไรก็ตาม แม้ไม่ใช่ผู้เขียนหลัก แต่ในฐานะ contributor กำลังทดลองใช้ wgpu เพื่อให้ได้ GPU acceleration ในระดับหนึ่งอยู่ ผู้เขียนหลักอยากควบคุมความซับซ้อน จึงยังไม่รู้ว่าจะไปได้ไกลแค่ไหนในทางปฏิบัติ
  • รู้สึกว่าน่าสนใจและน่าขอบคุณกับ ความกระตือรือร้น ของชุมชน Rust ที่แทบจะอยากเขียนทุกอย่างขึ้นมาใหม่