stable-diffusion.cpp - การรันอนุมานโมเดล Diffusion ที่เขียนด้วย C/C++
(github.com/leejet)- เป็นเครื่องมือสำหรับรัน การอนุมานโมเดล Diffusion ตระกูล SD, Flux และ Wan ด้วย C/C++ ล้วน โดยมุ่งเน้นการเป็นอิมพลีเมนต์แบบเบาโดยไม่มี external dependency
- การพัฒนาสร้างอยู่บน ggml และเป็นโครงสร้างแบบ Plain C/C++ ที่ทำงานในลักษณะเดียวกับ llama.cpp
- ขอบเขตของโมเดลที่รองรับแบ่งเป็นโมเดลภาพ โมเดลแก้ไขภาพ และโมเดลวิดีโอ โดยรองรับ SD1.x, SD2.x, SDXL, SD3/SD3.5, FLUX, Qwen Image, Wan2.1/Wan2.2, LTX-2.3 เป็นต้น
- ขอบเขตฟีเจอร์รองรับ PhotoMaker, Control Net สำหรับ SD 1.5, LoRA แบบ stable-diffusion-webui, LCM/LCM-LoRA, latent decoding บนพื้นฐาน TAESD, การอัปสเกลด้วย ESRGAN, negative prompt และ tokenizer ที่รองรับ token weighting
- แบ็กเอนด์ที่ใช้รันได้แก่ CPU, CUDA, Vulkan, Metal, OpenCL และ SYCL โดยฝั่ง CPU รองรับ AVX, AVX2 และ AVX512 บนสถาปัตยกรรม x86
- แพลตฟอร์มที่รองรับคือ Linux, Mac OS, Windows และ Android โดยบน Android ใช้งานผ่าน Termux และ Local Diffusion
- รองรับรูปแบบ weight ได้แก่
.ckpt,.pth,.pt,.safetensors,.ggufและโหมดแปลงไฟล์สามารถแปลง model weight เป็น.ggufหรือ.safetensors - ขั้นตอนใช้งานพื้นฐานคือดาวน์โหลดไบนารีที่ build ไว้ล่วงหน้าจาก releases page หรือ build จากซอร์ส จากนั้นดาวน์โหลด model weight แล้วรันสร้างภาพด้วยรูปแบบ
./bin/sd-cli -m ../models/v1-5-pruned-emaonly.safetensors -p "a lovely cat" - มีฟีเจอร์ปรับการใช้หน่วยความจำให้เหมาะสมด้วย Flash Attention และ VAE tiling processing ส่วนการจัดแบ็กเอนด์ขณะรันและการปรับปรุงประสิทธิภาพของ runtime กับพารามิเตอร์มีคู่มือแยกต่างหาก
- ตัวเลือกด้าน reproducibility แบ่งเป็น
--rng cudaและ--rng cpuโดยตั้งเป้าให้สอดคล้องกับ GPU RNG ของstable-diffusion-webuiและ RNG ของ ComfyUI ตามลำดับ - เอาต์พุต PNG จะฝังพารามิเตอร์การสร้างเป็นข้อความสตริงที่เข้ากันได้กับ webui
- มีโปรเจ็กต์ wrapper สำหรับ Golang, C#, Python, Rust และ Flutter/Dart และ Jellybox, Local Diffusion, LocalAI, KoboldCpp ก็ใช้
stable-diffusion.cppเป็นแบ็กเอนด์สำหรับสร้างภาพ - โปรเจ็กต์ยังอยู่ระหว่างการพัฒนาอย่างต่อเนื่อง และ API กับตัวเลือกบรรทัดคำสั่งอาจเปลี่ยนแปลงบ่อย
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
Llama.cpp/ggml เหมาะกับ LLM เป็นพิเศษ
เพราะต้องใช้หน่วยความจำมาก, quantization ได้ผลดี, การสร้างโทเคนเป็นแบบอนุกรมอย่างน่าทึ่งและติดอยู่กับแบนด์วิดท์หน่วยความจำ จึงเหมาะกับ CPU และยิ่งเหมาะกับการ inference แบบ pipeline CPU/GPU อันเป็นเอกลักษณ์ของ ggml
แต่ Stable Diffusion แตกต่างออกไป quantization ไม่ได้ผลดีเท่านั้น, UNet ใช้การคำนวณหนักมาก และการสร้างภาพแบบ batch ก็มีประสิทธิภาพและมีประโยชน์แม้กับผู้ใช้คนเดียว ดังนั้นจึงเหมาะกับ GPU/การ์ดจอออนบอร์ดมากกว่า และได้ประโยชน์มากจากความ hackable ของ implementation ใน Python
สำหรับ Stable Diffusion ผมคิดว่าแนวทางที่ถูกคือใช้ machine learning compiler เพื่อสร้างไฟล์ executable AITemplate เร็วมากอยู่แล้ว https://github.com/VoltaML/voltaML-fast-stable-diffusion, ส่วน TVM Vulkan ก็ดูมีอนาคตมาก หากมีใครทำ demo implementation ให้เสร็จสมบูรณ์ https://github.com/mlc-ai/web-stable-diffusion
แถมยังรักษาความ hackable ส่วนใหญ่ของ implementation PyTorch ล้วนไว้ได้ด้วย
เช่นตอนคอมไพล์รองรับ
GGML_CUBLASและได้ความเร็วเพิ่มขึ้นค่อนข้างดีเมื่อเทียบกับ C/C++ ล้วนแม้จะใช้เวลาหน่อย แต่ก็รันบนโน้ตบุ๊กเก่าได้
torch.compileด้วย และจำได้ว่าเคยทำเองจะลองดูว่าหาตัวเลขได้ไหม
ยอดเยี่ยมที่ implement ถึง CLIP ด้วย
ถ้าแยกมันออกมาต่างหากแล้วคอมไพล์เป็น implementation แบบ WebAssembly ก็น่าจะเจ๋ง
แก้ไข: ดูเหมือนมีคนทำ https://github.com/monatis/clip.cpp ไว้แล้ว ทีนี้ก็แค่ทำให้เป็น WebAssembly
รู้สึกน่าเสียดายเมื่อคิดว่าอาจมี โมเดลระดับ CLIP ที่พัฒนากว่านี้อยู่ในตู้เซฟลับที่ไหนสักแห่งแล้ว
แก้ไข: ผมไม่ได้หมายถึง CLIP-2 แต่หมายถึงความก้าวหน้าที่สำคัญในระดับเดียวกับ CLIP
การตั้งค่าง่ายจนน่าเหลือเชื่อ เลยลองทันทีเป็นครั้งแรก
อยากรู้ว่าความเร็วประมาณไหนถึงจะถือว่าปกติ
บน Linux ผมรันบน AMD Ryzen 7 5700G ด้วย
cmake .. -DGGML_OPENBLAS=ONและมีแค่กราฟิกออนบอร์ด ไม่มี GPU แยกเมื่อรัน
./bin/sd -m ../models/sd-v1-4-ggml-model-f32.bin -p "a lovely cat"แต่ละ sampling step ใช้เวลาประมาณ 12 วินาที และการ sampling ทั้งหมดใช้เวลา 246.40 วินาทีอยากรู้ว่านี่เป็นประสิทธิภาพที่ควรคาดหวังหรือเปล่า
แก้ไข: OpenBLAS ไม่ได้ติดตั้งอยู่ ดังนั้นแฟล็กนั้นจึงไม่มีผล
ตอนนั้นแทบทุกทางออกต้องใช้กอง dependency ของ Python และใช้เวลาติดตั้งนานมาก จนสุดท้ายล้มเหลวเพราะพื้นที่ดิสก์ไม่พอ
จริง ๆ แบบตรงตัวเลย คือแทนที่พื้นที่ดิสก์หลายกิกะไบต์ด้วย ไบนารีขนาด 799KB ไฟล์เดียว แถมถ้าใช้รูปแบบ Q8_0 ซึ่งดูเหมือนจะเร็วที่สุด ก็ยังประหยัดข้อมูลได้อีกประมาณ 2.3GB
แต่ดูเหมือนจะมีบั๊กกับขนาดภาพอื่นนอกจาก 512x512 ค่าเริ่มต้น บางขนาดอย่าง 544x544 มักทำให้ assert ล้มเหลว, ขนาดที่เล็กกว่า 512x512 บางครั้งสร้างภาพขยะ และขนาดที่เล็กกว่า 384x384 แทบจะเป็นแบบนั้นเสมอ
[0] https://news.ycombinator.com/item?id=32555608
implementation ด้าน AI ที่เป็น C/C++ มีเสน่ห์พิเศษบางอย่าง
โค้ดให้ความรู้สึกสะอาดและตรงไปตรงมา ทำให้ทั้งวงการ AI ดูเหมือนจับต้องและเรียนรู้ได้
เป็นเพราะ ecosystem ของ Python รกเกินไปหรือเปล่า?
เวอร์ชัน Python ก็ใช้โค้ด C และ C++ เพื่อความเร็ว แต่ที่นี่ทุกอย่างอยู่ในภาษาเดียว
เรียกได้ว่ามีปัจจัยสามอย่างที่ทำให้โค้ดสะอาดทำงานร่วมกัน
รู้สึกดีที่ได้เห็นคนสายแมชชีนเลิร์นนิงออกจาก Python แล้วใช้ภาษาที่ใช้ฮาร์ดแวร์ได้อย่างเหมาะสมที่สุด และไม่ต้องจัดสภาพแวดล้อมพิเศษเพื่อบิลด์และรัน
ก่อนอื่น โปรเจกต์ต้นทางนี้ไม่ได้ใช้ GPU เหมือน llama.cpp แต่โค้ดแมชชีนเลิร์นนิง Python ส่วนใหญ่ใช้ GPU การเขียนโค้ด Python ที่ใช้ GPU ได้อย่างเหมาะสมที่สุดไม่ใช่เรื่องยาก จะเรียก GPU ว่าเป็นสภาพแวดล้อมพิเศษสำหรับบิลด์และรันก็ได้ แต่สำหรับปัญหานี้ GPU น่าจะเหมาะกว่ามาก
อย่างที่สอง โปรเจกต์ต้นทางนี้ก็เหมือน llama.cpp คือหลังจากยืนยันแล้วว่าโมเดลเฉพาะอย่าง Stable Diffusion/LLaMA ทำงานได้ดี จึงค่อยสร้างโค้ดที่มีประสิทธิภาพและเฉพาะทางสูงขึ้นมา ในทางกลับกัน จุดที่ Python โดดเด่นคือ ขั้นตอนการทำโปรโตไทป์ ที่ยังหาโมเดลที่เหมาะสมไม่เจอ ผมยังไม่เคยเห็นการทำโปรโตไทป์ใน C++ ที่ง่ายและสะดวกขนาดนี้
ไม่ได้ตั้งใจจะลดคุณค่างานยอดเยี่ยมที่ทีม llama.cpp ทำในพื้นที่แมชชีนเลิร์นนิงบน CPU เพียงแต่ปัญหาที่แก้นั้นต่างกันโดยสิ้นเชิง
ข้างในเป็น CUDA, C, C++ ทั้งหมดมาตั้งนานแล้ว
Python เป็นแค่ กาว ที่มีประสิทธิภาพมากในการเชื่อมทุกอย่างเข้าด้วยกัน
นี่เป็นวิธีเดียวที่ผมเคยใช้รันโมเดลเหล่านี้ได้โดยไม่เจอปัญหาปวดหัว ความแตกต่างมันมากจริง ๆ ชุด CUDA กับ Linux ก็ไม่ดีนัก ส่วน AMD กับ Windows นี่แย่มาก คิดว่าคงไม่ใช่แค่ผมคนเดียว
สุดท้ายแล้วทั้งหมดเป็นปัญหาเรื่องแบนด์วิดท์หน่วยความจำหรือเปล่า?
สถาปัตยกรรม GPU ไม่ได้มีแค่พลังประมวลผล แต่ยังเป็นโครงสร้างที่วางหน่วยความจำสำหรับงานไว้ใกล้กับหน่วยคำนวณด้วย แต่ละหน่วยมีหน่วยความจำภายในที่ซิงก์กับหน่วยความจำส่วนกลาง นี่เป็นเหตุผลสำคัญที่ทำให้ GPU เก่งกับงานแบบนี้หรือเปล่า?
ดูเหมือนเป็น C++ แล้วทำไมถึงเขียนว่า C/C++?
วันนี้เห็น repo นี้แล้วดึงมาลอง บิลด์
.dylibบน Mac และสร้าง binding จากไฟล์ header ที่ให้มาด้วยเครื่องมือ ffi-gen ของ Dartกำลังทดลองใช้กับ Flutter และใช้ FFI เพราะไม่อยากเปิด subprocess
ผลลัพธ์คือเหลือไว้แต่อาการปวดหัวหนัก ๆ กับแอปที่พัง พรุ่งนี้จะลองใหม่ตอนหัวโล่ง
ถึงอย่างนั้น repo นี้เองก็ยอดเยี่ยม และบน M1 ก็รันได้ภายในไม่ถึง 10 นาทีด้วย f16
เห็นตัวอย่าง ระดับการ quantization หลายแบบแล้วค่อนข้างน่าประทับใจ
การเปลี่ยนจาก f16 เป็น q8_0 ดูเหมือนเป็นการเปลี่ยนทิศทางมากกว่าการสูญเสียคุณภาพ ผลลัพธ์ q5_1 ดูแยกจาก q8_0 ได้ยาก
ในโมเดลความแม่นยำสูงจะสูญเสียความ deterministic ไป แต่ในทางปฏิบัติอาจใช้งานได้ค่อนข้างดี
มี benchmark ไหม?
https://github.com/leejet/stable-diffusion.cpp/issues/1
cmake .. -DGGML_CUBLAS=ON -DCMAKE_CUDA_COMPILER=/opt/cuda/bin/nvccแล้วใช้ NVIDIA GeForce RTX 2060 SUPERโมเดลถูกแปลงเป็น FP16
ด้วยตัวเลือกนี้ เวลาแต่ละรอบ iteration อยู่ระหว่าง 8.5~9 วินาที และเวลารวมในการสร้างภาพหนึ่งภาพประมาณ 200 วินาที