Test-time scaling หรือศาสตร์ในการ Optimize Accuracy ของ LLM/Agent

 

เพื่อที่จะ Optimize Accuracy หรือเพิ่มความแม่นยำของ Agent/LLM เราจำเป็นจะต้องเข้าใจว่า Test-time Scaling คืออะไร ในบทความนี้เรามาเร่ิมจากทำความเข้าใจพื้นฐานและเทคนิคจากเปเปอร์ที่ชื่อว่า Self-Consistency พร้อมดู Code ตัวอย่างกัน

Table of Content

  1. พื้นฐาน
  2. ทำไมถึงเลือก Paper นี้มาเล่าให้ฟัง
  3. ที่มาและความสำคัญ
  4. Self-Consistency คืออะไร
  5. ลอง Self-Consistency
  6. การนำไปใช้
  7. ปิดท้าย

1. พื้นฐาน

Test-time scaling

คือเทคนิคนึงในการเพิ่มความแม่นยำหรือความฉลาดโดยการเพิ่ม “เวลา” หรือ “Resource”ในการตอบสนองของ LLM ผ่านการใช้งาน เรียกง่ายๆคือ ใช้เวลาตอบนานขึ้นเพื่อความแม่นยำที่มากขึ้น(นานขึ้น คือใช้ resource มากขึ้น)

Test-time = ตอนเรียกใช้ ตอน Inference จริง

Scaling = การ scale ความฉลาด

เทคนิคที่ถูกพูดถึงคู่กับ Test-time scaling คือ Train-time scaling หมายถึงการเพิ่มความฉลาดตอนที่ Train model นั่นเอง

ตัวอย่างที่เห็นได้ชัดของ Test-time scaling คือการใช้ Chain of Thought เพื่อให้ LLM มีการให้เหตุผลก่อนคำตอบสุดท้ายทำให้ความแม่นยำมากขึ้น

ส่วนตัวอย่างของ Train-time scaling คือยิ่งโมเดลใช้เวลาเทรนนานมากขึ้น ด้วย data ที่ใหญ่ขึ้น ทำให้โมเดลมีความฉลาดมากขึ้น

Chain of Thought(CoT)

ก่อนจะถลำลึกไปกว่านี้ อยากอธิบายก่อนว่า Chain of Thought หรือ CoT คือเทคนิคการเขียน Prompt ที่ “บังคับ” หรือ “โน้มน้าว” ให้ LLM คิดเป็นขั้นเป็นตอน โดยการทำสิ่งต่อไปนี้

  • เติม คำว่า “Think Step by Step” ต่อท้าย Prompt ที่ใช้
  • เติม “ตัวอย่าง” ของการคิดเป็นลำดับขั้น เหมือนอาจารย์คณิตศาสตร์สอนวิธีคิดโจทย์ตัวอย่าง แล้วให้นักเรียนใช้วิธีการเดียวกันกับโจทย์ที่ต่างออกไป

โดยเทคนิค Chain of Thought เป็นพื้นฐานสำคัญมากๆ เนื่องจากเทคนิคที่ใช้เพิ่มความแม่นยำต่างๆ ส่วนใหญ่เกิดมาจากงานวิจัยตัวนี้ รวมไปถึง การ Train พวก Reasoning model หรือ Non-Reasoning model ในยุคหลังๆ ที่ LLM เริ่มมีการทำ Chain of Thought โดยไม่ต้องพิมพ์ Prompt บอกกันแล้ว

มาต่อที่ Self-Consistency กันครับ

2. ทำไมถึงเลือก Paper นี้มาเล่าให้ฟัง

ต้องบอกก่อนว่า Paper ตัวนี้ถือได้ว่า “เก่า” ออกมาในช่วงปี 2023 ช่วงก่อน ChatGPT บูมซะอีก แต่ว่าเป็นเปเปอร์ตัวหนึ่งที่ถูก reference ในช่วงหลังๆ ระหว่างปี 2024–2025 บ่อยมากๆ โดยเฉพาะ paper ประเภท Test-time scaling จนผมต้องกลับมาอ่านและทบทวนดูว่า เจ้า Self-Consistency นี่คืออะไรกันแน่ แล้วมันใช้งานยังไง

สุดท้ายตัว method เป็นที่น่าสนใจกว่าผลลัพธ์ซะอีก เราไปดูกันครับ

Gemini generated art — Bill of Accuracy

3. ที่มาและความสำคัญ

ผู้เขียนกล่าวว่า Self-Consistency ตัวนี้ ช่วยเพิ่มความสามารถในการให้เหตุผลได้ดีขึ้นมากใน Benchmark ต่างๆ เช่น

  • GSM8K(ที่ใช้วัดความสามารถของ LLMในการแก้โจทย์คณิตศาสตร์ระดับประถมศึกษา) —GPT-3: CoT 14.6, Self-Consistency 23.4
  • StrategyQA(สำหรับทดสอบความสามารถในการให้เหตุผลแบบหลายขั้นตอน) — GPT-3:Cot 56.7, Self-Consistency 61.7

ในตอนแรกก็ไม่ได้ว้าวอะไรมากเพราะ Method/Paper ต่างๆ ก็มี base เป็น Chain-of-Thought (CoT) ต้องเอาชนะอยู่แล้ว

มาสะดุดตรง…

แรงบันดาลใจของ paper ตัวนี้เกิดจากว่า

วิธีการแก้ปัญหาสามารถมีได้หลายวิธี เพื่อแก้ปัญหาเดียว

4. Self-Consistency คืออะไร


Figure 1 from Original Paper

Self-Consistency เป็นเทคนิคที่ใช้ร่วมกับ Chain-of-Thought (CoT) prompting เพื่อเพิ่มความแม่นยำของคำตอบ โดยมีหลักการดังนี้:

ขั้นตอนการทำงาน

  1. ใช้ Chain-of-Thought Prompting: ให้ LLM คิดทีละขั้นตอน (think step by step) เพื่อแก้ปัญหา
  2. Generate หลายครั้ง: สำหรับโจทย์เดียวกัน ให้ LLM generate คำตอบพร้อมเหตุผล หลายๆ รอบ (เช่น 5–10 ครั้ง) โดยแต่ละครั้งอาจมีวิธีคิดที่แตกต่างกันไป
  3. นับเสียง (Majority Voting): นำคำตอบสุดท้ายทั้งหมดมาหาว่าคำตอบไหนปรากฏบ่อยที่สุด แล้วเลือกคำตอบนั้นเป็นคำตอบขั้นสุดท้าย
  • จุดประสงค์คือใช้ “ภูมิปัญญาของฝูงชน” (wisdom of crowds) — ถ้าหลายๆ วิธีคิดให้คำตอบเดียวกัน คำตอบนั้นน่าจะถูกต้อง

ตัวอย่าง

จากรูปด้านบน(Figure 1) มี Generate 3 ครั้ง ได้คำตอบ:

  • 18USD 2 ครั้ง
  • 26USD 1 ครั้ง

คำตอบสุดท้ายจึงเป็น 18USD

5. ลอง Self-Consistency + อธิบาย Code Example

เพื่อนๆสามารถลอง Self-Consistency ผ่าน Google Colab ตามขั้นตอนด้านล่างได้เลย

  1. เปิด Google Colab Notebook จาก https://github.com/GLOBAL-PALO-IT/th_LLM-PALO-IT-Paper-To-Code/blob/main/Self_Consistency_Code.ipynb
  2. เราจะใช้ GitHub Models Free Tier ที่ให้เราใช้ LLM Model แบบฟรีๆ
  3. Copy GitHub Token เก็บไว้ ใน Google Colab Secrets ตาม https://docs.github.com/en/github-models/quickstart#step-2-make-an-api-call
  4. กด “Run all” เพื่อดูผลลัพธ์
  5. ส่วนสำคัญของ Code ที่อยากให้เข้าใจมีดังนี้

Code Flowchart

Task หลักของ Agent เราในตัวอย่างนี้คือการแก้ Puzzle ที่เลือกมาจาก ZebraLogic Benchmark ซึ่งจะเห็นได้ว่า Prompt และ Extraction Code แต่ส่วนถูกเขียนมาเพื่อแก้โจทย์นี้โดยเฉพาะ

หลักการทำงานของโค้ดชุดนี้คือ

  1. Load dataset แล้ววน loop แต่ละโจทย์
  2. ส่งโจทย์แต่ละอันไปให้ function ที่รัน self-consistency — runSC
  3. ภายใน self-consistency function ก็จะมี loop ตามจำนวน sample ที่ set ไว้ ในแต่ละ loop ก็จะเป็นการเรียก LLM ด้วย Prompt ที่กำหนดแล้วทำการ extract คำตอบไว้
  4. หลังจากเก็บ sample ทั้งหมดในโจทย์นึงก็จะหา Common Answer/Majority Vote ออกมา
  5. แล้วนำไปตรวจคำตอบ แล้วเก็บไว้เป็นผลลัพธ์
  6. รวบรวมผลลัพธ์ทั้งหมดเพื่อสรุปเป็น Accuracy โดยรวม

วิเคราะห์โจทย์

การหาโจทย์มาเป็นตัวอย่างที่ชัดเจนสำหรับ paper ตัวนี้ค่อนข้างยากมาก เนื่องจาก benchmark ใน paper อย่าง GSM8Kโดน LLM ตั้งแต่ 3B เกมหมดแล้ว เลยต้องออกตามหาโจทย์แปลกๆ แต่ไม่ยากจนเกินไป และเลือกโมเดลที่เก่งพอดีๆ ไม่มากไป เพื่อหลีกเลี่ยงการตอบผิดหมดทุก Sample กับตอบถูกหมดทุก Sample

เลยมาลงที่ ZebraLogic Benchmark นี่แหละ

 ซึ่งตัว ZebraLogic เป็น Puzzle ประเภทนึงที่ทดสอบความสามารถในการให้เหตุผลอย่างเข้มข้น

 

ตัวอย่างรูป Logic Grid

Logic Grid Example

สามารถอ่านเพิ่มเติมได้ที่

https://en.wikipedia.org/wiki/Zebra_Puzzle

 
จากการทดสอบสรุปได้ว่า Model ส่วนใหญ่ยังไม่ได้ถูกเทรนด้วย Benchmark ตัวนี้มากนัก ดังนั้นจึงเหมาะอย่างยิ่งสำหรับการทดสอบของเรา

เรามาดูโค้ดส่วนที่สำคัญๆ กันต่อได้เลย

Prepare Prompt

# Prompt
# CoT Prompt
system_prompt="""
Each example/input/question is a Logic Grid Puzzle, also known as a Zebra Puzzle. In each puzzle, we are given N houses (numbered 1 to N from left to right) and M features for each house. There are N distinct values for each feature, and each house must have a unique value for each feature. Given a list of clues, one should be able to deduce a unique correct assignment of values. The logic grid puzzle is a typical Constraint Satisfaction Problem (CSP) and is often used to test humans' logical reasoning abilities in exams such as the Law School Admission Test (LSAT).

You always solve problem step by step
start answering with `## Reasoning steps:` and end with

## Final answer:
```json

````
"""

# Example Input Prompt
user_prompt="""
There are 2 houses, numbered 1 to 2 from left to right.
Each house is occupied by a different person.
Each house has a unique attribute for each of the following characteristics:
- Each person has a unique name: **Arnold, Eric**
- People own unique car models: **ford f150, tesla model 3**
- The people keep unique animals: **cat, horse**

**Clues**:
1. Eric is directly left of the person who owns a Tesla Model 3.
2. The person who keeps horses is in the first house.
"""

# Example Output Prompt
assistant_message="""
## Reasoning steps:

From Clue 1, we know that Eric is to the left of someone, so he must be the owner of House 1 because House 2 is the rightmost house.
Additionally, we know that the person in House 2 must be Arnold, and he owns a Tesla Model 3. Thus, Eric owns a Ford F150.
From Clue 2, we know that Eric keeps horses in House 1, which means the other house must keep cats. Finally, we arrive at the unique solution to this puzzle.
The solution is presented in table format:

## Final answer:

```json
{
"header":[
"Houses",
"Name",
"CarModel",
"Animal"
],
"rows":[
[
"1",
"Eric",
"ford f150",
"horse"
],
[
"2",
"Arnold",
"tesla model 3",
"cat"
]
]
}
```
"""

# lgp-test-2x2-33
# lgp-test-6x5-2
# lgp-test-2x4-33
# lgp-test-6x6-5
# lgp-test-3x3-24
question="""
Question:
There are 3 houses, numbered 1 to 3 from left to right, as seen from across the street. Each house is occupied by a different person. Each house has a unique attribute for each of the following characteristics:
- Each person has a unique name: `Peter`, `Eric`, `Arnold`
- Each person has a favorite color: `red`, `white`, `yellow`
- Each mother is accompanied by their child: `Fred`, `Meredith`, `Bella`

## Clues:
1. Arnold is the person whose favorite color is red.
2. The person's child is named Fred is somewhere to the left of Eric.
3. The person whose favorite color is red is in the second house.
4. The person's child is named Bella is in the first house.
5. The person who loves white is the person's child is named Meredith.

## Headers
"header": [
"House",
"Name",
"Color",
"Children"
]
"""

correct_answer="""
```json
{
"header": [
"House",
"Name",
"Color",
"Children"
],
"rows": [
["1", "Peter", "yellow", "Bella"],
["2", "Arnold", "red", "Fred"],
["3", "Eric", "white", "Meredith"]
]
}
```
"""

 

คือโค้ดส่วนที่เขียน Prompt

system_prompt คือส่วนที่เป็น Instruction หลัก

user_prompt โจทย์ตัวอย่าง

assistant_prompt คำตอบของโจทย์ตัวอย่าง เพื่อแสดงให้เห็นวิธีการคิด(CoT) และรูปแบบ output

question คือโจทย์ที่เราต้องการให้ LLM แก้

correct_answer คือเฉลยของโจทย์

Set Up Parameters

model_name = "openai/gpt-4.1-nano" # Choose not too smart model to see the diversity (phi-4,openai/gpt-4.1,deepseek/DeepSeek-R1-0528,xai/grok-3,openai/gpt-4.1-mini,deepseek/DeepSeek-V3-0324)
temperature=1.0 # Choose 1.0 for the most diverse answers
top_p=1.0 # Choose 1.0 for the most diverse answers

 

คือโค้ดส่วนที่เรา set up parameter และ configuration ต่างๆ

ส่วนที่อยากเน้นคือ

number_samples = 20 คือ จำนวน Sample ที่เราจะใช้ หรือจำนวนครั้งที่เราจะเรียก LLM เพื่อเอาคำตอบทั้งหมดมาให้ Majority Vote

model_name = “openai/gpt-4.1-nano คือโมเดลที่เราจะใช้ เราเลือกใช้ openai/gpt-4.1-nano เพราะว่า จำเป็นต้องใช้ Model ที่ไม่ฉลาดเกินไป เพื่อจะ demo ประโยชน์ ของ Self-Consistency ถ้าเลือกโมเดลฉลาดเกินที่ตอบถูกโดยไม่ต้องใช้ Self-Consistency อาจจะไม่เห็นประโยชน์ของมันเท่าที่ควร

temperature=1.0 และ top_p=1.0 คือค่าที่ควบคุมความ random ของคำตอบ เราตั้งไว้สูงสุดเพื่อให้ LLM มีคำตอบที่หลากหลายมากที่สุด

Call LLM in loop to get samples


responses = []
final_answers = [] # Collect all final answers
try:
for i in range(num_samples):
response = client.complete(
messages=messages,
temperature=temperature,
top_p=top_p,
model=model_name,
)
responses.append(response.choices[0])
answer = response.choices[0].message.content
final_ans = extract_final_answer_regex(answer)
if final_ans is None:
print(f"Warning: Could not extract answer from sample {i+1}")
clean_ans = final_ans.lower().replace(" ", "")
json_object = extract_json_from_string(clean_ans)
final_answers.append(json_object) # Store the final answer

print(f"\nSample {i+1}: {json_object}")
print(f"{'='*50}")
except Exception as e:
print(f"Error processing sample {i+1}: {e}")

 

โค้ดส่วนนี้เป็นส่วนที่วน loop เพื่อเรียก LLM เพื่อเก็บ Sample จากการเรียกหลายๆ ครั้ง พร้อมกับ Extract คำตอบสุดท้ายของแต่ละ Call

Get final Answer

if final_answers:

# Convert answers to normalized format for counting
normalized_answers = [normalize_answer(ans, for_comparison=False) for ans in final_answers]

answer_counts = Counter(normalized_answers)
most_common_string, count = answer_counts.most_common(1)[0]

# Convert back to original type for display
try:
most_common_normalized = json.loads(most_common_string)
# Find the original answer that matches this normalized version
for ans in final_answers:
if normalize_answer(ans, for_comparison=False) == most_common_string:
most_common_answer = ans
break
else:
most_common_answer = most_common_normalized
except (json.JSONDecodeError, TypeError):
most_common_answer = most_common_string

print(f"\n{'='*50}")
print(f"MOST COMMON ANSWER (appeared {count}/{num_samples} times):")
print(f"{most_common_answer}")
print(f"{'='*50}\n")

# Optional: print all answer frequencies
print("All answer frequencies:")
for ans_str, freq in answer_counts.most_common():
# Find the original answer that matches this normalized version
original_ans = None
for ans in final_answers:
if normalize_answer(ans, for_comparison=False) == ans_str:
original_ans = ans
break

if original_ans:
print(f"{original_ans} (appeared {freq} times)")
else:
try:
ans_display = json.loads(ans_str)
print(f"{ans_display} (appeared {freq} times)")
except (json.JSONDecodeError, TypeError):
print(f"{ans_str} (appeared {freq} times)")

 

นำคำตอบทุกอันจาก Sample ที่เรามีมาหา Common Answer หรือ Majority Vote

วิเคราะห์ผลลัพธ์

จากการทดสอบพบว่า ด้วย Dataset 20 row(grid_size 3*3 ล้วน) กับ Model gpt-4.1-nano

แบบ Sample = 1 หรือไม่ได้ใช้ Self-Consistency เลย ได้ Accuracy ที่ 35%

แบบ Sample = 5 หรือใช้ Self-Consistenccy ขนาดเท่ากับ 5 ได้ Accuracy ที่ 60%


Without Self-Consistency — 20 zebra logic problems

With Self-Consistency — 20 zebra logic problems

ทั้งนี้อยากให้ลองรันการทดลองนี้ด้วยตัวเอง แล้วลองปรับ model และ parameter และ grid_size เพื่อความเข้าใจที่ลึกซึ้งยิ่งขึ้น

6. การนำไปใช้

เมื่อไหร่ควรใช้ Self-Consistency

Self-Consistency เหมาะกับงานที่ต้องการความแม่นยำสูง และ End User ยินดีรอผลลัพธ์สักพัก ยกตัวอย่างเช่น งานวิเคราะห์ทางการเงิน หรือการตรวจสอบเอกสารสำคัญ ที่ความถูกต้องสำคัญกว่าความเร็ว หรือ Data Pipeline สามารถ manage ความคาดหวังของ user ได้ว่าต้องรอ


Accuracy vs Speed

เมื่อไหร่ไม่ควรใช้ Self-Consistency

ถ้าเป็น Agentic System ที่มีการ Execute หลาย step อย่าง Coding Agent ที่ต้องทำหลายขั้นตอนต่อเนื่อง การใช้ Sampling N ที่สูง (เช่น 10–20) ในทุก step จะทำให้ใช้เวลาและ Token เยอะมาก ไม่ค่อยเหมาะ กับ LLM Inference latency ณ ปัจจุบัน

การนำ Self-Consistency ไปใช้กับคำตอบแบบ Qualitative

จากตัวอย่างก่อนหน้านี้ เราใช้ Self-Consistency กับโจทย์คณิตศาสตร์ที่มีคำตอบเป็นตัวเลขชัดเจน การหา Majority Vote ทำได้ง่ายด้วยการเปรียบเทียบค่าตรงๆ

แต่ในความเป็นจริง โจทย์ส่วนใหญ่ไม่ใช่แค่ตัวเลข เช่น:

  • การตอบคำถามเกี่ยวกับสุขภาพ: “การออกกำลังกายตอนเช้ามีประโยชน์อย่างไร?”
  • งานเขียน: “เขียนอีเมลขอร้องให้ลูกค้าต่ออายุสัญญา”
  • งานวิเคราะห์: “สรุปประเด็นสำคัญจากรีวิวสินค้า 100 รีวิว”

คำตอบเหล่านี้อาจมีถ้อยคำต่างกัน แต่ให้ความหมายเดียวกัน เช่น:

คำตอบ 1: “ออกกำลังกายตอนเช้าช่วยเพิ่มพลังงานตลอดทั้งวัน”

คำตอบ 2: “การออกกำลังกายในช่วงเช้าทำให้มีแรงมากขึ้นตลอดวัน”

คำตอบ 3: “การวิ่งเช้าๆ ช่วยให้รู้สึกกระฉับกระเฉงตลอดวัน”

ทั้ง 3 คำตอบนี้ต่างกัน แต่ แก่นของเนื้อหาเหมือนกัน ถ้าเราเช็คแบบ exact match จะไม่เจอ Majority Vote เลย

วิธีแก้ปัญหา

ใช้ LLM อีกตัวเป็น Judge + Semantic Clustering เมื่อคำตอบไม่ใช่ตัวเลข เราต้องใช้ LLM อีกตัว มาช่วยตัดสินว่า(คล้ายๆ กับ LLM-as-a-Judge)คำตอบไหนมีความหมายเหมือนกัน หรือใช้เทคนิค Semantic Similarity เพื่อจัดกลุ่มคำตอบ


Qualitative answer

ข้อจำกัดของ Self-Consistency

  • Cost: ใช้ tokens เยอะกว่าปกติ N เท่า
  • ไม่เหมาะกับโจทย์ creative ที่ต้องการความหลากหลาย
  • ถ้า model มี systematic bias อาจได้คำตอบผิดซ้ำๆ กัน

 

7. ปิดท้าย

สำหรับผม Self-Consistency เป็นเปเปอร์ที่ช่วยให้การก้าวเข้าสู่โลก Test-time scaling ง่ายขึ้น เนื่องจากเห็นชัดเจนว่า Test-time เพิ่มขึ้นจาก Sample N ที่มากขึ้น และเห็นความแม่นยำที่เพิ่มมากขึ้นอีกด้วย

Self-Consistency ด้วยตัวมันเองมีประโยชน์ในการ Optimize Accuracy มาก ถ้าเทียบกับความง่ายในการใช้งาน เพียงแค่เพิ่ม Sample แล้วหา Majority Vote แต่หากยังไม่เพียงพอ หรือใครอยากศึกษาต่อผมแนะนำให้ปรับ parameter ในโค้ด และเลือกโมเดลหลายๆตัวมาลองดูผลลัพธ์เทียบกัน หรือจะลองเปลี่ยนโจทย์เป็น Level ยากสุก 6*6 ก็ได้ครับ จะได้เห็นความแตกต่างว่าในแต่ละโจทย์หรือแต่ละ parameter ได้ผลต่างกันยังไง

สุดท้ายถ้าอยากไปต่อให้ลอง Implement Tree of Thought เพิ่มเติมครับ

หากใครกำลังมองหา Consult มาช่วยสร้าง Agentic AI หรือช่วยปรับปรุง AI Agent ที่มีอยู่แล้วให้มี Accuracy และ Scale ได้

ติดต่อทีมงาน PALO IT Thailand ได้ที่ Facebook: PALO IT Thailand (https://www.facebook.com/PALOITTH)

Reference

Recommend Read

Ready to kickstart your next big project?
Let's innovate together.