1 คะแนน โดย GN⁺ 2024-02-25 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ย้าย flow การ inference ของ GPT-2 มาไว้ใน SQL โดยใช้เพียง PostgreSQL และ pgvector ตั้งแต่การ tokenize ไปจนถึง Transformer block และการสร้าง token ถัดไป
  • LLM เชิงสร้างสรรค์ใกล้เคียงกับ ฟังก์ชันกำหนดแน่นอน ที่ให้ความน่าจะเป็นของ token ผู้สมัครเหมือนเดิมเมื่อได้รับ input เดียวกัน จุดที่คำตอบต่างกันคือขั้นตอนการเลือกแบบสุ่มตามความน่าจะเป็นเพื่อหยิบ token ผู้สมัคร
  • การ implement แสดง BPE tokenizer ของ GPT-2, token 50,257 ตัว, embedding 768 มิติ, context 1,024 token, block 12 ชุด, attention head 12 หัว และ feedforward แบบ GELU ด้วย SQL query และ table
  • จำเป็นต้องมี ทางเลี่ยงเชิงปฏิบัติ ให้เข้ากับสภาพแวดล้อมฐานข้อมูล เช่น PostgreSQL regex ไม่รองรับ Unicode property และข้อจำกัดของ EXP ในการจัดการค่าขนาดเล็กมาก
  • ตัวอย่างสร้าง token 10 ตัวจาก "Happy New Year! I wish you" แล้ว output เป็น "Happy New Year! I wish you all the best in your new year!" โดยใช้เวลา 2 นาที 44 วินาทีในสภาพแวดล้อมของผู้เขียน

สร้าง GPT-2 inference pipeline ด้วย SQL

  • แม้ ChatGPT จะตอบว่า SQL ไม่เหมาะกับการ implement large language model แต่ผู้เขียนได้ implement GPT-2 inference pipeline ด้วย PostgreSQL SQL
  • คำอธิบายการ implement ที่ใช้อ้างอิงคือ GPT in 60 Lines of NumPy ของ Jay Mody และย้ายองค์ประกอบเดียวกันมาเป็น table และ query ในฐานข้อมูล
  • LLM เชิงสร้างสรรค์สามารถมองได้เหมือนฟังก์ชันรูปแบบ llm(prompt: str) -> list[tuple[str, float]]
    • input คือ text prompt
    • output คือรายการ string ผู้สมัครที่จะตามมาและ array ความน่าจะเป็น
    • หากคณิตศาสตร์ภายในและ parameter เหมือนกัน input เดียวกันจะคืนผลลัพธ์เดียวกัน
  • เหตุผลที่ผลิตภัณฑ์อย่าง ChatGPT อาจให้คำตอบต่างกันต่อคำถามเดียวกัน อยู่ที่การเลือกแบบสุ่มตามความน่าจะเป็นในขั้นตอน การเลือก token ถัดไป มากกว่าตัว model เอง

วนลูปสร้างข้อความ

  • กระบวนการสร้างมีโครงสร้างคือแปลง prompt เป็น array ของ token จากนั้นเรียก model ซ้ำ ๆ เพื่อเลือก token ถัดไปแล้วต่อท้าย prompt
  • flow พื้นฐานประกอบด้วยขั้นตอนต่อไปนี้
    • แปลง string เป็น array ของ token ID ด้วย tokenize(prompt)
    • gpt2(tokens) คำนวณความน่าจะเป็นสำหรับ token 50,257 ตัว
    • select_next_token(candidates) เลือก token ถัดไป
    • เพิ่ม token ที่เลือกเข้าไปใน array
    • หยุดเมื่อเข้าเงื่อนไข เช่น จำนวน token ที่กำหนด, timeout, stopword
    • กู้คืน array ของ token เป็น string ด้วย detokenize(tokens)
  • ลำดับ token ที่สะสมเช่นนี้สามารถกลายเป็นข้อความภาษาธรรมชาติที่มีคุณลักษณะเหมือนไวยากรณ์ โครงสร้าง ประโยค ความหมาย และการให้เหตุผล

implement BPE tokenizer ด้วย SQL

  • ก่อนเข้าสู่ neural network ข้อความต้องถูกแปลงเป็นรายการตัวเลข แต่หากใช้ Unicode codepoint ตรง ๆ จะไม่มีประสิทธิภาพทั้งในด้าน พื้นที่ token และความยาว
  • GPT-2 ใช้รูปแบบหนึ่งของ Byte pair encoding
    • token dictionary ใช้ codepoint 50,257 ตัว
    • รวม UTF-8 byte sequence และ token “end of text”
    • เริ่มจาก byte token 256 ตัว แล้วเพิ่มคู่ token ที่อยู่ติดกันและพบบ่อยเป็น token ใหม่
    • ทำการ merge นี้ซ้ำ 50,000 ครั้งเพื่อสร้าง token 50,256 ตัว แล้วเพิ่ม end-of-text token เป็นตัวสุดท้าย
  • GPT-2 tokenizer มีชั้นเพิ่มเติมที่ map byte เป็นอักขระ string โดย mapping นี้นิยามไว้ใน encoder.py ของ OpenAI GPT-2
  • ในการ implement ด้วย SQL นำ token dictionary ที่ดาวน์โหลดจาก OpenAI ใส่ใน table tokenizer และบันทึก byte-character mapping ใน table encoder
  • ตัวอย่าง "Mississippilessly" ใช้ recursive CTE เริ่มจาก byte เดี่ยว แล้ว merge คู่ที่อยู่ติดกันที่ดีที่สุดเท่าที่ merge ได้ซ้ำ ๆ
    • ในตัวอย่าง จำนวน token ลดจาก 17 เหลือ 5
    • ใช้พื้นที่ token ประมาณ 50k ของ GPT-2 แทนพื้นที่ codepoint ประมาณ 150k ของ Unicode
  • เมื่อต้องจัดการหลายคำ GPT-2 จะแบ่งข้อความด้วย regex แล้ว merge ภายในแต่ละคำ
    • PostgreSQL ไม่รองรับ Unicode character property ใน regex จึงมีการปรับ regex ดั้งเดิมของ GPT-2 บางส่วน
    • การปรับนี้อาจทำให้การรองรับ Unicode ที่เหมาะสมเสียหายได้
  • "PostgreSQL is great" ถูกแปลงเป็น [6307, 47701, 318, 1049] ใน SQL tokenizer
    • กลุ่ม token คือ Post, greSQL, Ġis, Ġgreat
    • Ġ แทนช่องว่าง

Embedding และ context window

  • token ID ไม่ได้ถูกใช้ในการคำนวณของ model โดยตรง แต่จะถูกแปลงเป็น embedding vector
  • GPT-2 ทำ embedding แยกกันสำหรับ token และตำแหน่ง
    • WTE คือ word token embedding เป็น matrix ขนาด 50257×768
    • WPE คือ word position embedding เป็น matrix ขนาด 1024×768
  • ในแต่ละตำแหน่งของ token จะนำ vector WTE และ vector WPE มาบวกกันเพื่อสร้าง input vector สำหรับขั้นต่อไป
  • เนื่องจาก WPE มีเพียง 1,024 ตำแหน่ง จำนวน token สูงสุดที่ใช้กับ prompt ของ GPT-2 ได้จึงเป็น 1,024
    • จำนวนนี้สอดคล้องกับ context window ของ LLM
    • เป็น hyperparameter ที่กำหนดตอนออกแบบ model และไม่เปลี่ยนด้วยการฝึก
  • การ implement ด้วย SQL ใช้ pgvector
    • แม้จะนิยาม vector operation กับ array โดยตรงเพื่อทำด้วย pure SQL ได้ แต่ประสิทธิภาพจะต่ำลง
    • เวอร์ชันแรกทำงานด้วย pure SQL function ได้ แต่ช้า

คลี่ self-attention เป็น SQL query

  • แกนหลักของ Transformer คือกลไก self-attention ซึ่งอิงจาก paper ปี 2017 Attention is all you need
  • attention ทำให้ token vector มีอิทธิพลต่อกัน เพื่อให้ข้อมูลจากส่วนต้นของ prompt ถูกส่งไปยัง vector สุดท้าย
  • การ implement GPT-2 ใช้ matrix Q, K, V จำนวน 12 ชุด
    • แต่ละชุดคือ attention head หนึ่งหัว
    • แต่ละ head มี 64 มิติ
    • c_attn เป็น linear transformation ขนาด 768×2304 และผลลัพธ์คือ vector 2304 มิติที่มี Q, K, V วางต่อกันตามแนวนอน
    • weight และ bias ถูกบันทึกใน table c_attn_w, c_attn_b
  • ก่อนคำนวณ attention จะใช้ layer normalization
    • parameter สำหรับ scale และ shift ถูกบันทึกใน table ln_1_g, ln_1_b
  • ใน causal self-attention จะใช้ causal mask เพื่อไม่ให้ token ด้านหลังส่งผลต่อ token ด้านหน้า
    • ผู้สมัคร token ถัดไปของ model จะถูกตัดสินจาก embedding สุดท้ายในท้ายที่สุด
    • การไหลของข้อมูลต้องมุ่งไปทาง vector สุดท้าย และค่าระหว่างทางของ vector สุดท้ายต้องไม่ส่งผลต่อ vector ก่อนหน้า
  • การ implement ด้วย SQL เลี่ยงปัญหาที่ PostgreSQL EXP ล้มเหลวกับค่าที่เล็กมากในการคำนวณ softmax โดยจัดการ input ที่น้อยกว่า -745.13 เป็น 0
  • ด้วย causal mask แม้จะต่อ token ใหม่ท้าย prompt ผลลัพธ์การคำนวณของ token ก่อนหน้าจะไม่เปลี่ยน
    • การ implement GPT-2 ดั้งเดิมใช้ประโยชน์จากคุณสมบัตินี้
    • การ implement ด้วย SQL ไม่ใช้การ reuse นี้เพื่อความเรียบง่าย

multi-head attention และ residual connection

  • ผลลัพธ์ attention จาก 12 head มี head ละ 64 มิติ และนำมาวางต่อกันตามแนวนอนเพื่อกลับเป็น 768 มิติ
  • จากนั้น project output ของ attention ด้วย linear transformation ที่ผ่านการฝึกและบันทึกไว้ใน c_proj_w, c_proj_b
  • ผลลัพธ์ของ multi-headed attention จะถูกบวกกลับด้วย input เดิม
    • residual connection นี้เป็นเทคนิคที่อยู่ใน paper Transformer ดั้งเดิม
    • ถูกนำเสนอเป็นการออกแบบเพื่อบรรเทาปัญหา vanishing gradient และ exploding gradient ระหว่างการฝึก

ขั้น feedforward และ Transformer block

  • หลัง attention จะตามด้วย feedforward neural network
  • ขั้น feedforward ของ GPT-2 ประกอบด้วย multi-layer perceptron 3 ชั้น
    • มิติเป็น 768 → 3072 → 768
    • ใช้ GELU เป็น activation function
  • parameter ของ linear transformation ถูกบันทึกใน table ต่อไปนี้
    • mlp_c_fc_w, mlp_c_fc_b
    • mlp_c_proj_w, mlp_c_proj_b
  • input ของ feedforward ก็ถูก normalize ก่อนด้วย parameter ln_2
    • ln_2_g, ln_2_b เก็บ scale และ shift
  • output ของ feedforward ก็ใช้ residual connection โดยบวก input กลับเข้าไป
  • การรวม attention + feedforward นี้คือ block หนึ่ง และ GPT-2 เชื่อม block 12 ชุดเข้าด้วยกันเหมือน pipeline
    • แต่ละ block มีชุด parameter ที่ผ่านการฝึกของตัวเอง
    • ใน SQL เชื่อม block ต่าง ๆ ด้วย recursive CTE
  • output ของ block สุดท้ายถูก normalize อีกครั้งด้วย parameter ln_f

แปลงกลับเป็น token ถัดไป

  • ใน output สุดท้าย vector 768 มิติที่ตำแหน่งสุดท้ายคือ embedding ของ token ถัดไป
  • เพื่อแปลง vector นี้กลับเป็น token จะใช้ matrix WTE ที่ใช้กับ embedding token ตอนแรกอีกครั้ง
  • โดยปกติแล้วการแปลงกลับแบบแม่นยำทำไม่ได้
    • embedding ที่ทำนายอาจไม่ตรงกับแถวใดแถวหนึ่งของ WTE อย่างพอดี
    • ดังนั้นจึงคำนวณ dot product กับ embedding ของแต่ละ token เพื่อหา token ที่ใกล้เคียง
  • ผลลัพธ์ dot product ระหว่าง WTE กับ embedding ที่ทำนาย คือคะแนน 50,257 ค่า หรือ logit
  • คะแนนเหล่านี้ถูกแปลงเป็นความน่าจะเป็นผ่าน softmax
    • จำนวนผู้สมัครอันดับต้น ๆ คือ top_n
    • ค่าที่ควบคุมการกระจายความน่าจะเป็นคือ temperature
    • ยิ่ง temperature สูง token ที่ไม่ใช่อันดับ 1 ก็ยิ่งมีโอกาสถูกเลือกมากขึ้น และ inference จะคาดเดาได้น้อยลง
  • ในตัวอย่าง "PostgreSQL is great" ผู้สมัคร token ถัดไป 5 อันดับแรกมีดังนี้
    • Ġfor
    • ,
    • .
    • Ġat
    • Ġto
  • เมื่อ temperature เปลี่ยนเป็น 0.5, 1, 2 การกระจายความน่าจะเป็น softmax ของผู้สมัครเดียวกันจะเปลี่ยนไป

ผล inference จริงและโค้ด

  • SQL สุดท้ายวนซ้ำกระบวนการเลือก token ตามความน่าจะเป็นและต่อท้าย prompt
  • ตัว model เองเป็นแบบกำหนดแน่นอน และองค์ประกอบที่ไม่กำหนดแน่นอนมีเพียงเลขสุ่มที่รวมอยู่ในการ เลือก token เท่านั้น
  • การตั้งค่าตัวอย่างมีดังนี้
    • prompt: "Happy New Year! I wish you"
    • จำนวน token ที่สร้าง: 10
    • temperature: 2
    • top_n: 1
    • ใช้ SETSEED(0.20231231)
  • ในสภาพแวดล้อมของผู้เขียน query ใช้เวลา 2 นาที 44 วินาที
  • ผลลัพธ์ output คือ "Happy New Year! I wish you all the best in your new year!"
  • query และโค้ดติดตั้งอยู่ใน GitHub repository quassnoi/explain-extended-2024

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

 
GN⁺ 2024-02-25
ความคิดเห็นจาก Hacker News
  • งดงามมาก ผมเองก็เคยขุดโพรงกระต่ายคล้าย ๆ กันด้วย SQLite แต่ยังไปไม่ถึงขั้นดึง neural network เข้ามาใช้
    ได้แรงบันดาลใจจากชุดเลกเชอร์ makemore[0] และพอผ่านไปประมาณ 1 ชั่วโมงก็จะเปลี่ยนจากวิธีนับไปเป็น neural network ซึ่งจุดที่ผมไปถึงก็น่าจะประมาณนั้น
    การลองแตกสิ่งนี้ออกมาเป็น relational model ถือเป็นแบบฝึกหัดที่ดีมากจริง ๆ
    [0] https://www.youtube.com/watch?v=PaCmpygFfXo

    • ถ้าดูต่อไป จะเห็นว่า neural network สรุปออกมาเป็น ตารางเดียวกับวิธีนับทุกประการ และตอน generate ก็ให้ผลลัพธ์เหมือนกันเป๊ะ
  • เป็นเดโมที่ดี แต่ในบทความ คำอธิบายเรื่อง causal masking ดูเหมือนจะปนระหว่างการเทรนกับการ inference
    causal masking มีจุดประสงค์เพื่อไม่ให้ “แอบดู” token ในอนาคตระหว่างการเทรน และในสถาปัตยกรรมแบบ GPT ก็ยังมีจุดประสงค์เพื่อบังคับความเป็น autoregressive ระหว่าง inference ด้วย
    ตอน inference ยังไงก็ใช้แค่ token สุดท้ายอยู่แล้ว ดังนั้น token นั้นจึง attend ไปยังลำดับ input ทั้งหมดได้ และดังนั้น token ถัดไปจึงไม่ได้ถูกกำหนดจาก embedding ของ token สุดท้ายเพียงอย่างเดียว

  • สงสัยว่านี่แสดง driver loop ของ GPT ได้ถูกต้องหรือเปล่า: ดูเหมือนเป็นโครงสร้างที่ tokenize prompt, เรียก gpt2(tokens) เพื่อได้ความน่าจะเป็นของ token 50,257 ตัว, เลือก token ถัดไป, ต่อเข้ากับรายการ token, ตรวจเงื่อนไขหยุด แล้วค่อย detokenize ตอนท้าย
    แต่แบบนี้ดูเหมือน state machine กำลัง implement อัลกอริทึม Shlemiel the painter เลยทำให้สงสัยเรื่องต้นทุนการคำนวณโดยเนื้อแท้ของงาน generate

    • สิ่งที่ผู้คนเรียกว่า context window ในโมเดลภาษาขนาดใหญ่ ผมเข้าใจว่าหมายถึงมีจำนวน token สูงสุดที่เก็บไว้ และ token ที่เก่าที่สุดจะถูกทิ้งไป
      หน้าต่างนั้นเป็น sliding window
    • ใช่ นั่นแหละคือ loop และเวทมนตร์ทั้งหมดอยู่ในฟังก์ชัน gpt2
    • นี่เป็นเพียงส่วนเล็กมากของอัลกอริทึม
      ใกล้เคียงกับการแสดงวิธีรวบรวม token ที่ generate แล้วให้เป็นประโยคเท่านั้น
  • แหล่งข้อมูลที่เกี่ยวข้อง: A GPT in 60 Lines of NumPy - https://news.ycombinator.com/item?id=34726115 - กุมภาพันธ์ 2023, ความคิดเห็น 146 รายการ

    • แหล่งข้อมูลนี้มีอยู่แล้วในช่วงต้นของบทความ
  • ในบริบทคล้ายกัน ผม implement GPT ทั้งหมดด้วย ฟังก์ชัน spreadsheet และทำวิดีโอ tutorial ที่ดูประกอบกันได้ด้วย
    https://spreadsheets-are-all-you-need.ai/

    • วิดีโอแรกยอดเยี่ยมมาก
      ในฐานะคนที่คิดว่า LLM ค่อนข้างเจ๋ง แต่ไม่ได้จำเป็นต้องเรียนรู้การทำงานจริงของมันเพื่ออาชีพ วิดีโอ 10 นาทีนี้สอนผมได้มากกว่าการอ่านคอมเมนต์ HN ที่เข้าใจยากกับบทความสื่อกระแสหลักแบบผิวเผินมาหลายปีเสียอีก
      พอเห็นจำนวน floating-point มหาศาลที่กองรอการคำนวณอยู่ ก็เข้าใจได้เป็นรูปธรรมขึ้นมากว่าทำไมเทคโนโลยีนี้ถึง กิน GPU มากขนาดนั้น
    • spreadsheet เป็นวิธีที่เป็นธรรมชาติในการ อธิบาย LLM
      ถ้าคำนวณอนุพันธ์ของแต่ละพารามิเตอร์ในแต่ละตัวอย่างการเทรน และแสดงอย่างชัดเจนว่ามัน map ไปยังพารามิเตอร์นั้นอย่างไร ก็น่าจะอธิบายกระบวนการเทรนได้ดีด้วย
  • ดีมาก สิ่งที่เมื่อหนึ่งปีก่อนยังดูเหมือน เวทมนตร์ บางอย่าง ตอนนี้ถูกอธิบายได้ดีขนาดนี้ แทบจะถึงระดับที่เด็กก็ทำตามได้

    • เวทมนตร์นี้ไม่ได้เริ่มเมื่อหนึ่งปีก่อน
      โมเดลที่บทความอธิบายคือ GPT-2 ซึ่งเปิดตัวต้นปี 2019
    • ไม่ใช่ระดับ “เด็กก็ทำตามได้” หรอก
      ถ้าจะเข้าใจบทความนี้ให้ถูกต้อง ต้องมี พื้นฐานวิทยาการคอมพิวเตอร์ ที่แน่น และแค่ชื่อเรื่องเองก็เข้าถึงยากสำหรับมนุษย์ 99% แล้ว
  • ผมหลีกเลี่ยง GPT และ LLM มาตลอด แต่แนวทางนี้ดูเหมือนจะสร้าง ความลื่นไหล ใน output ข้อความได้ระดับหนึ่ง แต่ยังไม่เห็นว่าจะมีความสามารถในการตีความคำถามและตอบคำถามด้วย
    อยากรู้ว่ามีบล็อกโพสต์ง่าย ๆ หรือคอร์สที่อธิบายว่ามันทำงานจริงอย่างไร หรือแสดง toy engine แบบ Python ไหม
    สื่อการสอนที่เคยเห็นมาจนตอนนี้มักเน้นว่าจะใช้แพลตฟอร์มอย่างไร และไม่ค่อยลงรายละเอียดการทำงานภายใน

  • น่าสนใจที่ machine learning สมัยใหม่ไม่ได้ต้องการ Turing completeness
    แต่ถึงอย่างนั้นเราก็ยังพิจารณาความเป็นไปได้ของ AGI อยู่ ถ้าสรุปได้ว่าไม่จำเป็นต้องมี Turing completeness ก็คงน่าสนุกทีเดียว

    • ดูเหมือนว่า Turing completeness จะจำเป็นนะ
      ด้วยเหตุผลง่าย ๆ คือผมสามารถไล่ตามการรันของ โค้ดที่ Turing-complete ในหัวได้
    • การ inference token เองไม่ได้ Turing-complete แต่ถ้า output สามารถสร้าง side effect ได้ เช่นแก้ prompt ของรอบถัดไปได้ นั่นก็เป็นอีกเรื่องโดยสิ้นเชิง
  • บทความยอดเยี่ยมมาก คำอธิบายแต่ละองค์ประกอบชัดเจนและค่อนข้างละเอียด อ่านแล้วดี
    แต่พอเผลอกด “+ expand source” แล้วเห็นอสูรกายอันน่าทึ่งนั้น ก็เริ่มเห็นด้วยกับ ChatGPT ที่บอกว่า “SQL ไม่เหมาะกับการ implement โมเดลภาษาขนาดใหญ่”

    • ผมก็กดเหมือนกัน แล้วหาวิธีพับกลับไม่เจอ
  • คำพูดที่ว่า “Unicode ธรรมดาไม่เข้ากับ neural network ได้ดี” นั้นไม่จริง ลองดู ByT5 เป็นตัวอย่างก็ได้
    สิ่งที่บทความเรียกว่า “alphabet” โดยทั่วไปเรียกว่า vocabulary และถ้าใช้ byte ของ UTF-8 เป็น vocabulary ก็จะมี token 256 ตัว ไม่ใช่ 149,186 ตัว
    ByT5 ทำแบบนั้นพอดี

    • ประเด็นไม่ใช่ว่าทำไม่ได้เลย แต่คือมันทำงานได้ไม่ดีเท่าแนวทางอื่นที่เรามี
      ข้อพิสูจน์คือโมเดลที่ประสิทธิภาพดีที่สุดในตลาดล้วนใช้ tokenization
      ไม่ใช่ความลับว่า tokenization โดยพื้นฐานแล้วค่อนข้างเป็น hack และในอุดมคติทุกคนก็อยากกำจัดมันออกไปในสักรูปแบบหนึ่งในอนาคต (https://twitter.com/karpathy/status/1657949234535211009)
      โดยหลักการแล้วสามารถชดเชยข้อบกพร่องของ byte-level tokenization ด้วยโมเดลที่ใหญ่ขึ้นและ context ที่ใหญ่ขึ้นได้ แต่ในทางปฏิบัติจะใช้ทรัพยากรมากกว่ามากในการเทรนโมเดลที่มีระดับ intelligence เท่ากัน
      แน่นอนว่าก็มีงานบางประเภทที่ tokenization กลับเป็นผลเสียต่อ intelligence เช่นการนับจำนวนตัวอักษรในคำ