ความแตกต่างระหว่าง Tensor Parallelism, Pipeline Parallelism และ Layer Sharding: เปรียบเทียบ 3 วิธีรัน LLM บน Multi-GPU

วิธีการขนานการประมวลผลแบบ Multi-GPU คือวิธีการกำหนดจุดแบ่งโมเดลและจังหวะการสื่อสารระหว่าง GPU เพื่อให้สามารถรัน LLM ที่มีขนาดใหญ่เกินกว่าจะบรรจุใน GPU เพียงใบเดียว หรือมีประสิทธิภาพช้าเกินไปหากใช้เพียงใบเดียว โดยวิธีการหลักที่นิยมใช้มี 3 รูปแบบ ได้แก่ Tensor Parallelism (TP), Pipeline Parallelism (PP) และการแบ่งเลเยอร์ (Layer Splitting) ที่ Ollama เป็นต้น
แม้ทั้ง 3 วิธีจะมีจุดร่วมกันคือการใช้ GPU หลายใบ แต่ในแง่ของความเร็วที่ได้รับ, การเชื่อมต่อที่จำเป็น และลักษณะการใช้งานที่เหมาะสมนั้นมีความแตกต่างกันอย่างมาก ปรากฏการณ์ที่ว่า "เพิ่ม GPU เป็น 2 ใบแล้วแต่การตอบสนองไม่เร็วขึ้น" สามารถอธิบายได้ด้วยความแตกต่างเหล่านี้
ในบทความนี้ เราจะสรุปกลไกของทั้ง 3 วิธีผ่านตารางเปรียบเทียบสำหรับเจ้าหน้าที่ฝ่ายเทคนิคที่กำลังพิจารณาการใช้งาน Local LLM ภายในองค์กรหรือเซิร์ฟเวอร์ GPU แบบ On-premise พร้อมทั้งอธิบายถึงความเหมาะสมในการใช้งานระหว่าง PCIe กับ NVLink รวมถึงวิธีการเลือกใช้งานระหว่าง vLLM และ Ollama
ความแตกต่างระหว่าง Tensor Parallelism, Pipeline Parallelism และ Layer Splitting

ความแตกต่างของทั้ง 3 วิธีอยู่ที่การเลือกว่าจะตัดโมเดล "ภายในเลเยอร์" หรือ "ระหว่างเลเยอร์" รวมถึงวิธีการทำงานของ GPU หลังจากที่ตัดแบ่งแล้ว ก่อนอื่นเรามาทำความเข้าใจภาพรวมจากตาราง จากนั้นจึงไปดูเหตุผลที่ทำให้เกิดความแตกต่างดังกล่าว
ตารางเปรียบเทียบ 3 วิธีการ
สรุปสั้นๆ คือ หากต้องการให้การตอบสนองต่อ 1 คำขอรวดเร็วขึ้น ให้เริ่มที่ Tensor Parallelism หากต้องการรองรับหลายคำขอพร้อมกันให้ได้มากขึ้น ให้เริ่มที่ Pipeline Parallelism และหากต้องการรันโมเดลที่มีขนาดใหญ่เกินกว่าจะใส่ใน GPU ใบเดียวได้ ให้เริ่มที่ Layer Sharding
| เกณฑ์การเปรียบเทียบ | Tensor Parallelism (TP) | Pipeline Parallelism (PP) | Layer Sharding |
|---|---|---|---|
| หน่วยที่แบ่ง | Weight matrix ภายในเลเยอร์ | กลุ่มของเลเยอร์ที่ต่อเนื่องกัน (Stage) | เลเยอร์ (กำหนดว่า GPU แต่ละใบรับผิดชอบเลเยอร์ใด) |
| การสื่อสารระหว่าง GPU | รวบรวมผลลัพธ์จากทุก GPU ในทุกเลเยอร์ (all-reduce) | ส่งต่อข้อมูลไปยัง GPU ข้างเคียงที่รอยต่อของ Stage | ส่งต่อข้อมูลไปยัง GPU ถัดไปที่รอยต่อของส่วนที่รับผิดชอบ |
| ปริมาณและความถี่ของการสื่อสาร | มาก | น้อย | น้อย |
| การตอบสนองต่อ 1 คำขอ | เร็วขึ้น | ไม่เร็วขึ้น | ไม่เร็วขึ้น |
| การประมวลผลหลายคำขอพร้อมกัน | เพิ่มขึ้น | เพิ่มขึ้น (โดยมีเงื่อนไขว่าต้องมีการส่งข้อมูลต่อเนื่อง) | ไม่ใช่จุดประสงค์หลัก |
| การเชื่อมต่อที่เหมาะสม | NVLink / NVSwitch | จัดการได้ง่ายแม้ใช้ PCIe | ไม่ค่อยมีปัญหากับ PCIe |
| ตัวอย่างการใช้งานหลัก | --tensor-parallel-size ของ vLLM | --pipeline-parallel-size ของ vLLM | ค่าเริ่มต้นของ Ollama, llama.cpp |
ในตารางนี้ แถวที่แสดงลักษณะเฉพาะของทั้ง 3 วิธีได้ดีที่สุดคือ "การตอบสนองต่อ 1 คำขอ" สำหรับวิธีที่แบ่งระหว่างเลเยอร์ต่อเลเยอร์นั้น ไม่ว่าจะเพิ่มจำนวน GPU เข้าไปกี่ใบก็ตาม 1 คำขอจำเป็นต้องผ่าน GPU ไปตามลำดับของเลเยอร์เท่านั้น มีเพียง Tensor Parallelism ที่แบ่งภายในเลเยอร์เท่านั้นที่สามารถให้ GPU ทุกใบช่วยกันคำนวณในเลเยอร์เดียวกันได้พร้อมกัน
ในทางกลับกัน Tensor Parallelism จะมีความถี่ในการสื่อสารที่สูงกว่ามาก วิธีไหนจะเร็วกว่ากันนั้น ขึ้นอยู่กับความเร็วของการเชื่อมต่อระหว่าง GPU และขึ้นอยู่กับว่าเป็นการใช้งานแบบคนเดียวหรือใช้งานพร้อมกันหลายคน
ความแตกต่างอยู่ที่ "จุดที่ตัด" และ "จังหวะที่สื่อสาร"
LLM มีโครงสร้างที่เกิดจากการซ้อนทับกันของชั้นที่มีรูปแบบเดียวกัน (Transformer blocks) หลายสิบชั้น ทุกครั้งที่มีการสร้างข้อความ 1 โทเค็น ข้อมูลนำเข้าจะผ่านชั้นเหล่านี้ตามลำดับตั้งแต่ชั้นแรกจนถึงชั้นสุดท้าย การทำความเข้าใจวิธีขนานการประมวลผล (Parallelization) จะง่ายขึ้นหากมองว่าเป็นการเลือกว่าจะ "ตัด" การซ้อนทับนี้ในทิศทางใด
การตัดระหว่างชั้นต่อชั้นเรียกว่า Pipeline Parallelism และ Layer Partitioning โดย GPU0 จะรับผิดชอบชั้นที่ 1-20 และ GPU1 รับผิดชอบชั้นที่ 21-40 เป็นต้น ดังนั้น GPU จะสื่อสารกันเฉพาะที่รอยต่อของการเปลี่ยนหน้าที่เท่านั้น แต่ในทางกลับกัน ในขณะที่กำลังคำนวณโทเค็นหนึ่งตัว GPU ที่ทำงานอยู่จะมีเพียงแค่ตัวที่รับผิดชอบชั้นที่โทเค็นนั้นอยู่เท่านั้น
อีกวิธีหนึ่งคือการตัดภายในแต่ละชั้นที่เรียกว่า Tensor Parallelism ซึ่ง GPU ทุกตัวจะรับผิดชอบทุกชั้นและแบ่งงานคำนวณในแต่ละชั้นกันคนละเล็กละน้อย ทุกคนสามารถทำงานได้พร้อมกัน แต่จะต้องนำผลลัพธ์ระหว่างทางมาแชร์กันก่อนที่จะไปยังชั้นถัดไป
หากเปรียบเทียบให้เห็นภาพ Layer Partitioning และ Pipeline Parallelism ก็เหมือนกับการทำงานแบบสายพานที่แบ่งหน้าที่กันตามขั้นตอน ส่วน Tensor Parallelism ก็เหมือนกับการที่ทุกคนช่วยกันแบ่งงานในขั้นตอนเดียว และต้องมารวมตัวกันเพื่อตรวจสอบคำตอบทุกครั้งที่จบขั้นตอนนั้นๆ ความเร็วของทั้งสองวิธีขึ้นอยู่กับเวลาที่ใช้ในการรวมตัวกัน หรือก็คือความเร็วของการเชื่อมต่อระหว่าง GPU นั่นเอง
Tensor Parallelism (TP) ทำงานอย่างไรและเร็วขึ้นที่จุดไหน?

Tensor Parallelism คือวิธีการแบ่งการคำนวณในชั้นเดียว (layer) ออกไปให้ GPU หลายตัวช่วยกันประมวลผล แม้จะช่วยลดเวลาในการตอบสนองต่อ 1 คำขอได้ แต่ประสิทธิภาพก็จะขึ้นอยู่กับความเร็วในการเชื่อมต่อระหว่าง GPU เป็นอย่างมาก
แบ่งเมทริกซ์ภายในเลเยอร์และรวมผลลัพธ์ในแต่ละชั้น
หัวใจสำคัญของการคำนวณในแต่ละเลเยอร์ของ LLM คือการคูณเมทริกซ์ขนาดใหญ่ ในการทำ Tensor Parallelism เราจะแบ่งเมทริกซ์น้ำหนัก (weight matrix) นี้ออกเป็นส่วนๆ ตามแนวคอลัมน์หรือแถว แล้วกระจายไปยัง GPU แต่ละตัวเพื่อให้แต่ละตัวคำนวณในส่วนที่รับผิดชอบไปพร้อมๆ กัน หากค่า TP=4 GPU แต่ละตัวจะถือครองน้ำหนักประมาณ 1 ใน 4 ของแต่ละเลเยอร์
การคำนวณเฉพาะส่วนที่ได้รับมอบหมายเพียงอย่างเดียวไม่สามารถทำให้ผลลัพธ์ของเลเยอร์นั้นเสร็จสมบูรณ์ได้ จึงจำเป็นต้องมีการสื่อสารเพื่อนำผลลัพธ์ระหว่างทางของ GPU ทั้งหมดมารวมกันและแจกจ่ายผลลัพธ์ที่เหมือนกันกลับไปให้ทุกคน ซึ่งกระบวนการนี้เรียกว่า all-reduce ในงานวิจัย Megatron-LM ซึ่งเป็นดีไซน์ที่เป็นตัวแทนของ Tensor Parallelism ได้อธิบายโครงสร้างที่ใน 1 เลเยอร์ของ Transformer จะมีการทำ all-reduce 2 ครั้งในการคำนวณแบบ Forward (หลังกลไก Attention และหลังเลเยอร์ Fully Connected) งานวิจัย Megatron-LM
หากมีเลเยอร์หลายสิบชั้น ทุกครั้งที่มีการสร้าง 1 โทเค็น GPU ทั้งหมดจะต้องทำการประสานจังหวะกันเป็นจำนวน 2 เท่าของจำนวนเลเยอร์นั้น ถึงกระนั้นเหตุผลที่ 1 คำขอ (request) ทำงานได้เร็วขึ้นก็เพราะ GPU แต่ละตัวอ่านเฉพาะน้ำหนักในส่วนที่ตนรับผิดชอบเท่านั้น ในขั้นตอนการสร้างทีละโทเค็น เวลาที่ใช้ในการอ่านน้ำหนักจากหน่วยความจำมักจะส่งผลกระทบมากกว่าการคำนวณโดยตรง และการแบ่งภาระการอ่านนี้ไปยัง GPU หลายตัวจึงช่วยลดเวลาดังกล่าวได้
ใน vLLM เราจะระบุค่าผ่าน --tensor-parallel-size 4 ซึ่งค่า "TP=4" ที่พบเห็นได้บ่อยก็คือการตั้งค่านี้เอง
ทำไมการเชื่อมต่อแบบ PCIe ถึงขยายประสิทธิภาพได้ยาก
all-reduce คือการสื่อสารที่ต้องรอให้ผลลัพธ์ของทุกคนครบถ้วนก่อนจึงจะดำเนินการขั้นต่อไปได้ เนื่องจากกระบวนการนี้เกิดขึ้นทุกครั้งในแต่ละเลเยอร์ แม้เวลาที่รอในแต่ละครั้งจะน้อย แต่เมื่อสะสมรวมกันก็จะส่งผลโดยตรงต่อเวลาในการตอบสนอง (Response Time) ด้วยเหตุนี้ ประสิทธิภาพของ Tensor Parallelism จึงขึ้นอยู่กับความเร็วของเส้นทางที่เชื่อมต่อระหว่าง GPU เป็นสำคัญ
เพื่อเป็นข้อมูลอ้างอิง NVIDIA ได้ประกาศความเร็วแบนด์วิดท์การเชื่อมต่อสำหรับ GPU รุ่น H100 (รุ่น SXM) สำหรับศูนย์ข้อมูลไว้ที่ 900GB/s ผ่าน NVLink และ 128GB/s ผ่าน PCIe Gen5 ข้อมูลจำเพาะของ NVIDIA H100 แม้ประสิทธิภาพการสื่อสารจริงจะขึ้นอยู่กับการกำหนดค่า แต่จะเห็นได้ว่ามีความแตกต่างกันอย่างมากในเรื่องของขนาดช่องทางสื่อสาร
เอกสารอย่างเป็นทางการของ vLLM ยังแนะนำด้วยว่า สำหรับโหนด GPU ที่ไม่มี NVLink เช่น L40S การใช้ Pipeline Parallelism แทน Tensor Parallelism จะช่วยให้ได้ Throughput ที่สูงขึ้นและมี Overhead ในการสื่อสารที่น้อยลง เอกสารอย่างเป็นทางการของ vLLM
ในการกำหนดค่าที่ใช้ GPU สำหรับเวิร์กสเตชันหรือเกมมิ่งหลายตัว มักพบว่า GPU เชื่อมต่อกันผ่าน PCIe เท่านั้น แม้ Tensor Parallelism จะสามารถทำงานได้ในสภาพแวดล้อมดังกล่าว แต่ก็ไม่ได้หมายความว่าจะทำงานได้เร็วขึ้นตามจำนวน GPU ที่เพิ่มขึ้นเสมอไป เนื่องจากผลลัพธ์จะเปลี่ยนไปตามขนาดของโมเดลและจำนวนคำขอ (Request) พร้อมกัน ดังนั้น วิธีที่แน่นอนที่สุดคือการวัดเวลาในการตอบสนองต่อ 1 คำขอในสภาพแวดล้อมขององค์กรท่านเองก่อนตัดสินใจเลือกใช้งาน
Pipeline Parallelism (PP) ทำงานอย่างไรและเหมาะกับอะไร?

Pipeline Parallelism คือวิธีการแบ่งโมเดลออกเป็นกลุ่มชั้น (Stage) แล้วส่งผ่านข้อมูลไปตามลำดับเหมือนสายพานในโรงงาน วิธีนี้มีการสื่อสารน้อยและจัดการได้ง่ายแม้ผ่าน PCIe แต่จะไม่ช่วยให้การตอบสนองต่อ 1 คำขอเร็วขึ้น
ส่งกลุ่มเลเยอร์ตามลำดับและประมวลผลต่อเนื่องเพื่อเพิ่มประสิทธิภาพ
ตัวอย่างเช่น หากนำโมเดล 80 เลเยอร์มาทำ Pipeline Parallelism โดยใช้ GPU 4 ตัว จะถูกแบ่งออกเป็น 4 สเตจ ได้แก่ เลเยอร์ที่ 1-20, 21-40, 41-60 และ 61-80 เมื่อ GPU แต่ละตัวคำนวณสเตจของตนเองเสร็จสิ้น ก็เพียงแค่ส่งผลลัพธ์ระหว่างทางไปยัง GPU ตัวถัดไปเท่านั้น ไม่จำเป็นต้องทำ all-reduce เพื่อรวบรวมผลลัพธ์จากทุกตัว และการสื่อสารจะจำกัดอยู่เพียงการส่งข้อมูลไปยัง GPU ข้างเคียงที่รอยต่อของสเตจเท่านั้น นี่คือเหตุผลที่วิธีนี้สามารถจัดการได้ง่ายแม้ผ่าน PCIe
วิธีนี้จะแสดงประสิทธิภาพสูงสุดในสถานการณ์ที่มีคำขอ (Request) เข้ามาอย่างต่อเนื่อง ในขณะที่ GPU1 กำลังคำนวณส่วนต่อขยายของ "Request A" ตัว GPU0 ก็สามารถเริ่มทำงานในส่วนแรกของ "Request B" ถัดไปได้ หากมีการส่งคำขอเข้ามาอย่างต่อเนื่อง ทุกสเตจก็จะทำงานโดยไม่หยุดพัก ส่งผลให้ Throughput (ปริมาณงานที่ประมวลผลได้ในเวลาที่กำหนด) โดยรวมเพิ่มขึ้น
ในทางกลับกัน หากกระแสงานขาดช่วงก็จะเกิดการรอคอยขึ้น ช่วงเวลาที่ GPU บางตัวต้องว่างงานในช่วงเริ่มต้นและช่วงสิ้นสุดของไปป์ไลน์จะถูกเรียกว่า "Bubble" ในงานวิจัยของ GPipe ซึ่งนำเสนอ Pipeline Parallelism สำหรับการเรียนรู้ (Training) ได้มีการลดการรอคอยนี้โดยการแบ่งมินิบัตช์ (Mini-batch) ออกเป็นไมโครบัตช์ (Micro-batch) ที่เล็กลงแล้วจึงส่งผ่านไป งานวิจัยของ GPipe สำหรับเซิร์ฟเวอร์ที่ใช้ในการอนุมาน (Inference Server) ก็ใช้แนวคิดเดียวกัน ยิ่งสามารถเรียงลำดับคำขอจำนวนมากเพื่อส่งผ่านไปได้มากเท่าใด ประสิทธิภาพก็จะยิ่งสูงขึ้นเท่านั้น
ไม่เร็วขึ้นหากมีเพียง 1 คำขอ
สิ่งที่ควรระวังสำหรับ Pipeline Parallelism คือความเร็วในการประมวลผลเมื่อมีเพียง 1 คำขอ (Request) เท่านั้น โดย Request A จำเป็นต้องผ่าน Stage 1 ถึง 4 ตามลำดับ และในขณะใดขณะหนึ่งจะมีเพียง GPU เดียวเท่านั้นที่ประมวลผล A อยู่ เมื่อเทียบกับการคำนวณทุก Layer ด้วย GPU ประสิทธิภาพเท่ากันเพียงใบเดียว เวลาที่ใช้ในการคำนวณแทบจะไม่ต่างกัน หรืออาจจะนานกว่าเล็กน้อยเนื่องจากเวลาที่ใช้ในการส่งผ่านข้อมูลระหว่าง Stage
ซึ่งตรงกันข้ามกับ Tensor Parallelism ที่สามารถลดเวลาการตอบสนองของ 1 คำขอให้สั้นลงได้ หากเป็นการใช้งานภายในบริษัทที่มีคนใช้แชทเพียงไม่กี่คนและมีคำขอพร้อมกันน้อย ข้อดีของ Pipeline Parallelism ในด้าน Throughput ก็แทบจะไม่มีผล เพราะ Stage หลังๆ จะต้องรอเวลาที่ Stage ก่อนหน้าคำนวณเสร็จนานขึ้น
สรุปคือ Pipeline Parallelism ไม่ใช่รูปแบบที่เน้น "ทำให้คนเดียวเร็วขึ้น" แต่เป็นรูปแบบที่เน้น "จัดการงานจำนวนมากโดยไม่ให้หยุดชะงัก" จึงจะคุ้มค่าที่จะเลือกใช้เมื่อมีการใช้งานในลักษณะที่คำขอซ้อนทับกัน เช่น การเป็น API ภายในที่ถูกเรียกใช้งานจากหลายแผนก หรือการประมวลผลเอกสารจำนวนมากในตอนกลางคืน หากต้องการให้การตอบสนองของแชทที่ใช้คนเดียวเร็วขึ้น การพิจารณาใช้ Tensor Parallelism หรือเลือกโมเดลที่มีขนาดพอดีกับ GPU 1 ใบจะเป็นทางลัดที่ดีกว่า
Layer Splitting (ค่าเริ่มต้นของ Ollama) มีไว้เพื่ออะไร?

การแบ่งเลเยอร์ (Layer Splitting) คือวิธีการจัดสรรชั้นข้อมูลให้กับ GPU แต่ละตัวและส่งผ่านการประมวลผลไปตามลำดับ ซึ่งจะเข้าใจได้ง่ายขึ้นหากมองว่าวิธีนี้ไม่ได้มีไว้เพื่อเพิ่มความเร็ว แต่มีไว้เพื่อรวม VRAM เข้าด้วยกันสำหรับรันโมเดลที่ไม่สามารถโหลดลงใน GPU เพียงใบเดียวได้
จุดประสงค์ไม่ใช่ความเร็ว แต่เป็นการปรับให้พอดีกับ VRAM
ในการแบ่งเลเยอร์ (Layer splitting) เราเพียงแค่กำหนดหน้าที่ว่า "เลเยอร์นี้อยู่ที่ GPU0 ส่วนเลเยอร์ถัดไปอยู่ที่ GPU1" โดยการคำนวณขณะสร้างโทเค็นในการสนทนาหนึ่งครั้งจะเป็นแบบลำดับเกือบทั้งหมด เมื่อ GPU0 ทำงานในส่วนของตนเสร็จแล้วจะส่งต่อให้ GPU1 ซึ่งในระหว่างนั้น GPU0 ก็ต้องรอรอบถัดไป ดังนั้น แม้จะใช้ GPU หลายตัว ก็แทบไม่สามารถคาดหวังให้การตอบกลับหนึ่งครั้งเร็วขึ้นได้เลย
ในทางกลับกัน วิธีนี้ช่วยให้สามารถใช้ VRAM ของ GPU แต่ละตัวรวมกันได้ ตัวอย่างเช่น หากมี GPU ขนาด 24GB จำนวน 2 ตัว ก็สามารถแบ่งน้ำหนัก (Weights) และ KV cache ที่ไม่สามารถบรรจุลงใน GPU ตัวเดียวได้ไปไว้บน GPU ทั้งสองตัวได้ การสื่อสารจะเกิดขึ้นเฉพาะที่รอยต่อของหน้าที่ที่แบ่งไว้เท่านั้น จึงเป็นวิธีที่แทบไม่มีปัญหาแม้จะเชื่อมต่อผ่าน PCIe ก็ตาม
หากดูจากรูปแบบแล้ว วิธีนี้จะคล้ายกับ Pipeline Parallelism มาก แต่ความแตกต่างอยู่ที่จุดประสงค์ โดย Pipeline Parallelism เป็นวิธีที่ตั้งสมมติฐานว่าจะต้องมีการไหลของคำขอหลายรายการอย่างต่อเนื่องเพื่อเติมเต็มแต่ละสเตจ ในขณะที่การแบ่งเลเยอร์เป็นการจัดวางโดยมีจุดประสงค์หลักเพื่อบรรจุโมเดลลงไปให้ได้ก่อน
ใน llama.cpp ค่าเริ่มต้นของ --split-mode คือ layer ซึ่งจะแบ่งเลเยอร์และ KV cache ไปไว้บน GPU แต่ละตัว คำอธิบาย llama-server ของ llama.cpp นอกจากนี้ยังมีการนำกลไกที่แบ่งแบตช์ขนาดใหญ่ เช่น พร้อมต์ที่ยาวๆ ออกเป็นส่วนย่อยๆ เพื่อประมวลผลซ้อนทับกันระหว่าง GPU เข้ามาใช้ด้วย การเปลี่ยนแปลงที่เพิ่ม Pipeline Parallelism เข้าไปใน llama.cpp ส่วนที่มีโอกาสจะเร็วขึ้นได้คือส่วนของการประมวลผลแบบแบตช์เหล่านี้ ส่วนการสร้างทีละโทเค็นนั้นยังคงต้องดำเนินการตามลำดับ
หากสาเหตุที่โมเดลไม่สามารถบรรจุลงใน GPU ตัวเดียวได้มาจากขนาดของโมเดล ก่อนที่จะเพิ่ม GPU ก็คุ้มค่าที่จะตรวจสอบว่าสามารถลดหน่วยความจำที่จำเป็นด้วยการ ทำควอนไทเซชัน (Quantization) ได้หรือไม่
Ollama ให้ความสำคัญกับ "ถ้าใส่ได้ในใบเดียว ก็ใช้ใบเดียว"
Ollama ไม่ได้กระจายโมเดลไปยัง GPU หลายตัวในสภาพแวดล้อม Multi-GPU เสมอไป ตามที่ระบุไว้ใน FAQ อย่างเป็นทางการ เมื่อ Ollama โหลดโมเดล มันจะเปรียบเทียบ VRAM ที่จำเป็นกับพื้นที่ว่างที่มีอยู่ หากโมเดลสามารถบรรจุลงใน GPU เพียงตัวเดียวได้ทั้งหมด มันจะโหลดลงใน GPU ตัวนั้นเพียงตัวเดียว เนื่องจากวิธีนี้ช่วยลดปริมาณข้อมูลที่ต้องรับส่งผ่านบัส PCI ในระหว่างการประมวลผล ซึ่งโดยปกติแล้วจะเป็นวิธีที่เร็วที่สุด และจะกระจายโมเดลไปยัง GPU ทุกตัวที่ใช้งานได้ก็ต่อเมื่อโมเดลไม่สามารถบรรจุลงใน GPU ตัวเดียวได้เท่านั้น Ollama FAQ
กล่าวคือ Ollama จะใช้การแบ่งเลเยอร์ (Layer splitting) ก็ต่อเมื่อ "ไม่สามารถบรรจุลงใน GPU ตัวเดียวได้" เป็นหลัก ดังนั้นหากคุณเพิ่ม GPU ตัวที่สองเข้าไปแล้วการใช้งาน GPU ไม่เปลี่ยนแปลง นั่นอาจเป็นเพราะโมเดลยังคงบรรจุอยู่ใน GPU ตัวเดียวได้พอดี
หากคุณต้องการให้กระจายโมเดลไปยัง GPU ทุกตัวเสมอ ให้เปิดใช้งานตัวแปรสภาพแวดล้อม OLLAMA_SCHED_SPREAD ซึ่งในซอร์สโค้ดของ Ollama ได้นิยามไว้ว่าเป็นค่าคอนฟิกสำหรับ "การวางโมเดลข้าม GPU ทั้งหมดเสมอ" คำนิยามตัวแปรสภาพแวดล้อมของ Ollama อย่างไรก็ตาม การกระจายโมเดลไม่ได้ทำให้การตอบสนองในหนึ่งคำขอเร็วขึ้น และตามที่ FAQ อย่างเป็นทางการได้อธิบายไว้ โดยปกติแล้วการโหลดโมเดลที่สามารถบรรจุลงใน GPU ตัวเดียวได้ไว้ใน GPU ตัวเดียวนั้นจะเร็วกว่า
หากคุณต้องการเร่งความเร็วในการตอบสนองต่อ 1 คำขอด้วยการใช้ GPU หลายตัว คุณควรพิจารณาใช้เซิร์ฟเวอร์สำหรับการอนุมาน (Inference server) ที่รองรับ Tensor Parallelism เช่น vLLM แทน
ควรเลือกวิธีไหนสำหรับสภาพแวดล้อมของคุณ?

ในการเลือกใช้งาน ให้พิจารณาตามลำดับดังนี้เพื่อช่วยให้ตัดสินใจได้ง่ายขึ้น: "สามารถใส่ใน 1 แผ่นได้หรือไม่", "ความเร็วต่อ 1 คำขอ (Request) กับปริมาณการประมวลผลพร้อมกัน สิ่งใดสำคัญกว่ากัน" และ "การเชื่อมต่อระหว่าง GPU คืออะไร"
ลำดับการตัดสินใจ
สิ่งแรกที่ต้องตรวจสอบคือโมเดลสามารถโหลดลงใน GPU เพียงใบเดียวได้หรือไม่ หากโหลดได้ โดยพื้นฐานแล้วไม่ควรแบ่งส่วนโมเดล หากมี GPU หลายใบ การรันโมเดลเดียวกันแยกกันในแต่ละ GPU แล้วกระจายคำขอ (Request) จะช่วยเพิ่มปริมาณการประมวลผลโดยรวมได้อย่างตรงไปตรงมามากกว่า
หากไม่สามารถโหลดลงใน GPU ใบเดียวได้ ให้กำหนดวัตถุประสงค์ถัดไปดังนี้:
- ต้องการให้การตอบสนองต่อผู้ใช้หนึ่งคนรวดเร็วขึ้น: พิจารณาใช้ Tensor Parallelism แต่มีข้อแม้ว่าต้องมีการเชื่อมต่อที่รวดเร็ว เช่น NVLink
- ต้องการรองรับคำขอจำนวนมากพร้อมกัน: พิจารณาใช้ Pipeline Parallelism ซึ่งสามารถเห็นผลได้ง่ายแม้จะเป็นการเชื่อมต่อผ่าน PCIe
- ขอแค่ให้รันได้ก็พอและมีผู้ใช้งานน้อย: การแบ่ง Layer ก็เพียงพอแล้ว หากใช้ Ollama ระบบจะตั้งค่าเป็นรูปแบบนี้โดยอัตโนมัติโดยไม่ต้องปรับแต่ง
สุดท้ายให้ตรวจสอบการเชื่อมต่อ หากเป็นเซิร์ฟเวอร์ที่เชื่อมต่อด้วย NVLink หรือ NVSwitch ตัวเลือกแรกที่ควรพิจารณาคือ Tensor Parallelism แต่หากเป็นโครงสร้างที่มีเพียง PCIe การใช้ Pipeline Parallelism หรือการแบ่ง Layer จะเป็นทางเลือกที่สมเหตุสมผลกว่า สำหรับโมเดลที่มีขนาดใหญ่เกินกว่าจะบรรจุใน 1 โหนดได้ เอกสารอย่างเป็นทางการของ vLLM ได้ระบุวิธีการผสมผสานโดยใช้ Tensor Parallelism ภายในโหนด และใช้ Pipeline Parallelism ระหว่างโหนด เอกสารอย่างเป็นทางการของ vLLM
หากพบผลลัพธ์ที่ว่า "เพิ่ม GPU แล้วแต่ไม่เร็วขึ้น" การทบทวนการจับคู่ระหว่างวัตถุประสงค์และวิธีการตามลำดับนี้ จะช่วยให้เห็นเหตุผลที่แท้จริงได้
ตัวอย่างการกำหนดค่าตามการใช้งาน
เมื่อนำไปประยุกต์ใช้จริง การผสมผสานรูปแบบต่างๆ ต่อไปนี้ถือเป็นจุดเริ่มต้นที่ดีครับ
สำหรับการทดสอบหรือการใช้งานในกลุ่มเล็กๆ หากใช้เวิร์กสเตชันที่ติดตั้ง GPU แบบ PCIe จำนวน 2 ใบ การใช้การแบ่งเลเยอร์ (Layer Splitting) ของ Ollama จะเป็นวิธีที่ง่ายที่สุด หากกังวลเรื่องความล่าช้าในการตอบสนอง การลองใช้โมเดลที่ผ่านการทำ Quantization หรือโมเดลที่มีขนาดพอดีกับ GPU ใบเดียว จะให้ผลลัพธ์ที่ดีกว่าการเพิ่มจำนวน GPU ครับ
หากต้องการให้บริการในรูปแบบ API แก่หลายแผนกภายในองค์กร และมีเซิร์ฟเวอร์ GPU ที่เชื่อมต่อด้วย NVLink การใช้ Tensor Parallelism ของ vLLM จะเป็นตัวเลือกที่มีประสิทธิภาพ ซึ่งช่วยให้ลดเวลาในการตอบสนองต่อ 1 คำขอ พร้อมทั้งรองรับการประมวลผลแบบขนานได้ด้วย
สำหรับเซิร์ฟเวอร์ที่ติดตั้ง GPU หลายใบแต่ไม่มี NVLink และมีคำขอเข้ามาพร้อมกันจำนวนมาก การลองใช้ Pipeline Parallelism ของ vLLM ก็นับว่าคุ้มค่าครับ ดังที่กล่าวไปข้างต้น เอกสารอย่างเป็นทางการของ vLLM ก็แนะนำวิธีนี้สำหรับโหนดที่ไม่มี NVLink เช่นกัน
ไม่ว่าจะเลือกโครงสร้างแบบใด วิธีที่แน่นอนที่สุดคือการวัดผลด้วยภาระงาน (Load) ของบริษัทคุณเอง โดยต้องตรวจสอบทั้งเวลาในการตอบสนองต่อ 1 คำขอ และจำนวนคำขอที่สามารถประมวลผลพร้อมกันได้ โดยใช้ความยาวของ Prompt ที่ใกล้เคียงกับการใช้งานจริง สำหรับรายละเอียดว่าควรวางตำแหน่งของ Local LLM เมื่อเทียบกับ Cloud API อย่างไร สามารถอ่านเพิ่มเติมได้ที่ การเปรียบเทียบการใช้งาน Local LLM / SLM ครับ
ข้อผิดพลาดที่พบบ่อยในการอนุมานด้วย Multi-GPU คืออะไร?

ความล้มเหลวส่วนใหญ่มักเกิดขึ้นเมื่อลักษณะของวิธีการและวัตถุประสงค์ไม่สอดคล้องกัน ต่อไปนี้คือตัวอย่างที่พบบ่อย 2 ประการและวิธีหลีกเลี่ยงครับ
คิดว่าการเพิ่ม GPU จะทำให้การตอบสนองเร็วขึ้น
สิ่งแรกที่ต้องทำความเข้าใจคือ ความเชื่อที่ว่าหากเพิ่ม GPU เป็น 2 หรือ 4 ใบแล้ว ความเร็วในการตอบสนองต่อ 1 คำขอจะเร็วขึ้นตามสัดส่วนนั้น ตามที่ได้กล่าวไปข้างต้น ในการแบ่งเลเยอร์ (Layer Parallelism) และไปป์ไลน์แบบขนาน (Pipeline Parallelism) การคำนวณของ 1 คำขอจะดำเนินไปตามลำดับ ดังนั้นแม้จะเพิ่มจำนวน GPU เข้าไป เวลาในการตอบสนองก็แทบจะไม่เปลี่ยนแปลง ส่วนในเทนเซอร์แบบขนาน (Tensor Parallelism) หากเป็นการเชื่อมต่อผ่าน PCIe อาจเกิดการรอคอยการสื่อสารที่เพิ่มขึ้น ทำให้ผลลัพธ์ไม่เป็นไปตามจำนวน GPU ที่เพิ่มขึ้น
เพื่อหลีกเลี่ยงความเข้าใจผิดนี้ ก่อนที่จะเพิ่ม GPU คุณควรตัดสินใจให้ชัดเจนด้วยตัวเลขว่า "ต้องการทำให้สิ่งใดเร็วขึ้น" ไม่ว่าจะเป็นเวลาในการตอบสนองต่อ 1 คำขอ หรือจำนวนคำขอที่สามารถประมวลผลได้ใน 1 วินาที ซึ่งวิธีการเลือกและวิธีการวัดผลจะแตกต่างกันออกไป
ในการวัดผล ให้ใช้ Prompt เดียวกัน ความยาวของผลลัพธ์เท่ากัน และจำนวนการเชื่อมต่อพร้อมกันที่เท่ากันทั้งก่อนและหลังการเพิ่ม GPU สำหรับเวลาในการตอบสนอง หากแยกดูระหว่างเวลาจนกว่าตัวอักษรแรกจะปรากฏ กับความเร็วในการสร้างข้อความหลังจากนั้น จะช่วยให้เข้าใจได้ง่ายขึ้นว่าส่วนใดที่มีการเปลี่ยนแปลง หากผลลัพธ์ไม่เป็นไปตามที่คาดหวัง สิ่งที่ควรทำก่อนคือการทบทวนความเหมาะสมระหว่างวิธีการที่ใช้กับวัตถุประสงค์ ส่วนการเพิ่ม GPU เข้าไปอีกนั้นให้ถือเป็นทางเลือกสุดท้าย
จำนวน GPU ไม่สอดคล้องกับจำนวน Tensor Parallelism
Tensor Parallelism ยังมีข้อจำกัดเรื่องที่ไม่สามารถเลือกจำนวน GPU ได้อย่างอิสระ เนื่องจากกลไก Attention ถูกแบ่งออกเป็น "Heads" หลายชุด และในการทำ Tensor Parallelism จะต้องกระจาย Head เหล่านี้ไปยัง GPU แต่ละตัวอย่างเท่าๆ กัน ใน vLLM หากจำนวน Head ทั้งหมดไม่สามารถหารด้วยจำนวน Tensor Parallel ลงตัว จะเกิดข้อผิดพลาด Total number of attention heads (X) must be divisible by tensor parallel size (Y). และไม่สามารถเริ่มต้นการทำงานได้ การอภิปรายที่เกี่ยวข้องใน vLLM
บางครั้งเราอาจคิดว่ามี GPU อยู่ 3 ตัว ดังนั้นจะตั้งค่า TP=3 แต่กลับต้องมาเจอกับกำแพงนี้ สำหรับโมเดลที่มีจำนวน Head ไม่สามารถหารด้วย 3 ลงตัว เช่น 32 หรือ 64 จะไม่สามารถใช้ TP=3 ได้
วิธีแก้ไขมีอยู่ 2 ทางเลือก ทางเลือกแรกคือการปรับจำนวนให้หารลงตัว เช่น ใช้ TP=2 แล้วนำ GPU ที่เหลือไปใช้งานอย่างอื่น ส่วนอีกทางเลือกหนึ่งคือการใช้ Pipeline Parallelism เนื่องจาก Pipeline Parallelism จะแบ่งงานตามหน่วยของ Layer จึงไม่ได้รับผลกระทบจากข้อจำกัดเรื่องจำนวน Head ก่อนที่จะตัดสินใจซื้อ GPU เพิ่มเติม การตรวจสอบจำนวน Head ของโมเดลที่จะใช้งานในไฟล์ตั้งค่า (Configuration file) จะช่วยให้คุณหลีกเลี่ยงการลงทุนที่สูญเปล่าได้
คำถามที่พบบ่อยเกี่ยวกับการอนุมานด้วย Multi-GPU

สุดท้ายนี้ เราจะมาตอบคำถามที่มักจะเกิดขึ้นบ่อยเมื่อพิจารณาเรื่องโครงสร้าง
Q1: ใช้ Tensor Parallelism กับ GPU ที่เชื่อมต่อผ่าน PCIe 2 ใบได้หรือไม่?
สามารถทำได้ครับ เซิร์ฟเวอร์สำหรับการอนุมาน (Inference server) อย่าง vLLM สามารถรันด้วย Tensor Parallelism ได้แม้ในโครงสร้างที่ไม่มี NVLink แต่ปัญหาคือเรื่องของประสิทธิภาพ เนื่องจาก all-reduce ในแต่ละเลเยอร์จะต้องผ่าน PCIe ทำให้เกิดการรอคอยการสื่อสารที่เพิ่มขึ้น
แม้ว่าการตอบสนองต่อ 1 คำขออาจจะเร็วขึ้นบ้างในบางกรณี แต่หากเป็นการใช้งานที่มีคำขอเข้ามาพร้อมกันจำนวนมาก การใช้ Pipeline Parallelism จะช่วยให้ได้ Throughput ที่สูงกว่าตามที่เอกสารอย่างเป็นทางการของ vLLM แนะนำ วิธีที่แน่นอนที่สุดในการตัดสินใจว่าแบบใดเหมาะสมกว่า คือการทดลองเปรียบเทียบทั้งสองวิธีกับโมเดลและโหลดงานจริงครับ
Q2: ทำไมการเพิ่ม GPU ใบที่ 2 ใน Ollama ถึงไม่ทำให้เร็วขึ้น?
สาเหตุที่เป็นไปได้มี 2 ประการ ประการแรกคือกรณีที่โมเดลมีขนาดพอดีกับ GPU 1 ใบ และ Ollama ทำงานโดยใช้ GPU เพียงใบเดียวตามปกติ ประการที่สองคือกรณีที่แม้จะมีการกระจายงานไปยัง GPU 2 ใบ แต่เนื่องจากเป็นการแบ่งเลเยอร์ (layer splitting) การคำนวณเพื่อตอบสนองในแต่ละครั้งจึงยังคงดำเนินไปตามลำดับ ทั้งสองกรณีนี้ไม่ใช่ความผิดปกติ แต่เป็นการทำงานตามการออกแบบของ Ollama
Q3: สามารถใช้ Tensor Parallelism ร่วมกับ Pipeline Parallelism ได้หรือไม่?
สามารถนำมาใช้งานร่วมกันได้ โดยใน vLLM คุณสามารถกำหนดค่าทั้งสองอย่างพร้อมกันได้ ซึ่งในเอกสารอย่างเป็นทางการได้ระบุตัวอย่างเช่น --tensor-parallel-size 4 --pipeline-parallel-size 2 ซึ่งเป็นการเชื่อมต่อ Tensor Parallel ขนาด 4 GPU เข้ากับ Pipeline Parallel จำนวน 2 ขั้น ในกรณีนี้จะใช้ GPU ทั้งหมด 8 ใบ
รูปแบบที่พบได้บ่อยคือการใช้ Tensor Parallel ภายในเซิร์ฟเวอร์เครื่องเดียวที่เชื่อมต่อกันด้วย NVLink และใช้ Pipeline Parallel สำหรับเส้นทางที่ช้ากว่าในการเชื่อมต่อระหว่างเซิร์ฟเวอร์ โดยมีแนวคิดคือการจัดสรรกระบวนการที่มีการสื่อสารข้อมูลสูงไปยังเส้นทางที่รวดเร็ว และกระบวนการที่มีการสื่อสารน้อยไปยังเส้นทางที่ช้ากว่า หากโมเดลของคุณสามารถรองรับได้ภายในเซิร์ฟเวอร์เครื่องเดียว แนะนำให้เริ่มต้นด้วยวิธีใดวิธีหนึ่งก่อน แล้วค่อยพิจารณาการใช้งานร่วมกันเมื่อทรัพยากรไม่เพียงพอ
บทสรุป: เลือกวิธีตามจุดประสงค์ ทั้งความเร็ว ปริมาณการประมวลผลพร้อมกัน และการบรรจุโมเดล

Tensor Parallelism (เทนเซอร์พาราเลลลิซึม) จะทำการแบ่งเลเยอร์ภายในและคำนวณพร้อมกันบน GPU ทุกตัว เพื่อลดเวลาในการตอบสนองต่อ 1 คำขอ แต่เนื่องจากต้องมีการทำ all-reduce ในทุกเลเยอร์ จึงจำเป็นต้องมีการเชื่อมต่อที่รวดเร็วอย่าง NVLink เป็นพื้นฐาน ส่วน Pipeline Parallelism (ไปป์ไลน์พาราเลลลิซึม) เป็นวิธีที่ส่งผ่านกลุ่มของเลเยอร์ตามลำดับ ซึ่งมีการสื่อสารน้อยกว่าและรองรับการใช้งานผ่าน PCIe ได้ง่ายกว่า โดยจะแสดงประสิทธิภาพได้ดีที่สุดเมื่อมีการประมวลผลคำขอจำนวนมากอย่างต่อเนื่อง สำหรับ Layer Splitting (การแบ่งเลเยอร์) เป็นเพียงการจัดสรรเลเยอร์ไปไว้ใน GPU ต่างๆ โดยมีจุดประสงค์เพื่อรวม VRAM ให้เพียงพอต่อการโหลดโมเดลขนาดใหญ่มากกว่าการเน้นความเร็ว
ในสภาพแวดล้อมแบบ Multi-GPU นั้น Ollama จะโหลดโมเดลลงใน GPU เพียงใบเดียวหากโมเดลมีขนาดพอดี แต่หากไม่พอ Ollama จะใช้วิธี Layer Splitting เพื่อกระจายโมเดลไปยัง GPU ทุกตัว หากคุณพบว่าการเพิ่ม GPU แล้วไม่ได้ช่วยให้ประมวลผลเร็วขึ้น ให้ลองตรวจสอบกลไกนี้เป็นอันดับแรก
เมื่อต้องการตัดสินใจเลือกการตั้งค่า ให้ตรวจสอบตามลำดับดังนี้: โมเดลสามารถโหลดลงใน GPU ใบเดียวได้หรือไม่, ต้องการความเร็วในการตอบสนองสำหรับผู้ใช้ 1 คน หรือต้องการรองรับผู้ใช้จำนวนมาก, และการเชื่อมต่อระหว่าง GPU เป็นแบบใด หากคุณกำลังพิจารณาการนำ Local LLM มาใช้ในองค์กรหรือวางแผนการตั้งค่า GPU Server คุณสามารถติดต่อเราได้ทันที
ผู้เขียน・ผู้ตรวจสอบ
Yusuke Ishihara
เริ่มเขียนโปรแกรมตั้งแต่อายุ 13 ปี ด้วย MSX หลังจบการศึกษาจากมหาวิทยาลัย Musashi ได้ทำงานพัฒนาระบบขนาดใหญ่ รวมถึงระบบหลักของสายการบิน และโครงสร้าง Windows Server Hosting/VPS แห่งแรกของญี่ปุ่น ร่วมก่อตั้ง Site Engine Inc. ในปี 2008 ก่อตั้ง Unimon Inc. ในปี 2010 และ Enison Inc. ในปี 2025 นำทีมพัฒนาระบบธุรกิจ การประมวลผลภาษาธรรมชาติ และแพลตฟอร์ม ปัจจุบันมุ่งเน้นการพัฒนาผลิตภัณฑ์และการส่งเสริม AI/DX โดยใช้ generative AI และ Large Language Models (LLM)


