Temperature 0 mà kết quả vẫn khác: vì sao chạy eval một lần chưa đủ để kết luận
Một prompt, chạy 1.000 lần, ra 80 câu trả lời khác nhau. Nếu bộ eval của bạn chỉ chạy một lần rồi so điểm, rất có thể bạn đang so nhiễu với nhiễu.
Tóm tắt nhanh
- Thinking Machines chạy cùng một prompt 1.000 lần ở temperature 0 trên Qwen3-235B, thu về 80 output khác nhau.
- Nguyên nhân chính là tải server làm batch size thay đổi, còn seed chỉ được hứa ở mức "cố gắng".
- Một nghiên cứu đo được độ chính xác lệch tới 15% giữa các lần chạy bình thường, nên một lần chạy eval không phải là điểm số đáng tin.
- 1Bạn gửi requestCùng prompt, temperature 0, cùng seed
- 2Server gom batchTải thay đổi nên batch size mỗi lần một khác
- 3Kernel ra số khácKết quả tính toán phụ thuộc batch size, thiếu batch invariance
- 4Token đổi chỗHai token gần ngang xác suất có thể đổi thứ hạng
- 5Output rẽ nhánhMột token khác kéo phần còn lại của câu trả lời đi theo
Batch size phía server thay đổi theo tải, và một sai lệch nhỏ trong phép tính có thể đẩy cả câu trả lời sang hướng khác.
Đồ hoạ: FDE Times
Thử hình dung: thứ Năm, khách hàng hỏi một câu tưởng dễ trả lời, prompt mới có tốt hơn prompt cũ không. Bạn chạy bộ eval 200 câu, prompt cũ được 84%, prompt mới được 80%, và đã định viết vào báo cáo là “rollback”. Rồi bạn chạy lại prompt cũ, cấu hình y hệt, temperature 0, và lần này nó chỉ được 81%.
Lỗi không nằm trong code của bạn. Thinking Machines viết thẳng rằng trong thực tế, API của LLM vẫn không tất định, kể cả khi temperature bằng 0. Với một FDE, mọi quyết định “giữ hay bỏ” dựa vào một lần chạy eval vì thế đều có thể chỉ là nhiễu.
Một prompt, 1.000 lần chạy, 80 đáp án
Thí nghiệm của Thinking Machines rất đơn giản: gửi cùng một prompt tới Qwen3-235B 1.000 lần ở temperature 0. Kết quả thu về 80 completion khác nhau, và completion phổ biến nhất chỉ xuất hiện 78 lần. Các output giống hệt nhau ở 102 token đầu, rồi rẽ nhánh từ token thứ 103.
Hãy làm phép tính. 78 trên 1.000 là 7,8%. Nếu bạn lấy một output “chuẩn” làm đáp án mẫu rồi so khớp chính xác, hơn 92% số lần chạy sẽ không khớp, dù phía bạn không thay đổi gì.
Bạn có thể cho rằng bộ eval chỉ hỏi câu có đáp án ngắn thì sẽ an toàn hơn. Chưa chắc. Một nghiên cứu trên arXiv (2408.04667) thử 5 LLM đã cấu hình để cho kết quả xác định, trên 8 tác vụ phổ biến, mỗi cấu hình 10 lần chạy, cả zero-shot lẫn few-shot.
Kết quả: độ chính xác dao động tới 15% giữa các lần chạy bình thường, không can thiệp gì, và không mô hình nào cho độ chính xác lặp lại nhất quán. Thứ bị lệch ở đây là chính con số accuracy bạn định đưa vào báo cáo, không chỉ câu chữ của output.
Vì sao temperature 0 không cứu được bạn?
Cách giải thích phổ biến là “GPU chạy song song, số thực dấu phẩy động cộng không theo đúng thứ tự, nên kết quả lệch”. Simon Willison, khi tóm tắt bài của Thinking Machines, nhấn mạnh rằng giả thuyết này không phải nguyên nhân chính.
Thủ phạm thật nằm ngoài request của bạn. Tải trên server quyết định batch size mà kernel chạy, tải lại thay đổi không đoán trước được, và kết quả tính toán phụ thuộc vào batch size. Thinking Machines gọi đó là thiếu tính bất biến theo batch (batch invariance), chứ không phải các request rò thông tin sang nhau.
Từ đó dễ hình dung chuyện gì xảy ra với token: một sai lệch số học rất nhỏ đủ để hai token có xác suất gần bằng nhau đổi chỗ, và một token khác ở giữa câu kéo cả phần còn lại đi theo hướng khác. Đó đúng là hình ảnh 80 completion cùng chung 102 token đầu rồi tách ra.
Cách sửa tận gốc là kernel bất biến theo batch; mã đi kèm bài của Thinking Machines làm Qwen3-8B chạy tất định trên vLLM. Nhưng việc này chỉ làm được ở phía hạ tầng.
Nếu khách hàng tự host mô hình, đây là câu hỏi đáng đặt ra với đội inference; còn nếu bạn gọi API bên ngoài, hãy coi tính không tất định là điều kiện có sẵn và thiết kế eval quanh nó.
Seed và fingerprint: hữu ích, nhưng chỉ là “cố gắng”
Nhiều kỹ sư nghĩ đặt seed là xong. Cookbook của OpenAI dùng chữ rất cẩn thận: hệ thống sẽ “cố gắng hết sức” để lấy mẫu tất định. Tài liệu Azure OpenAI nói rõ hơn: ngay cả khi seed và system_fingerprint trùng nhau giữa các lần gọi, vẫn không hiếm khi thấy response khác nhau.
system_fingerprint vẫn đáng giá. Theo Cookbook của OpenAI, nó đại diện cho cấu hình backend mà mô hình đang chạy, và nên được dùng cùng seed để phát hiện khi backend đổi theo cách có thể ảnh hưởng tính tất định.
Azure còn cảnh báo thêm hai điều cho eval. Mặc định, hỏi cùng một câu nhiều lần thì nhiều khả năng nhận về câu trả lời khác nhau, nên muốn tái lập phải giữ nguyên seed lẫn các tham số như temperature, max_tokens. Và max_tokens càng lớn thì response càng kém tất định.
Indeed Engineering đi xa hơn một bước: phương sai của chính output mô hình có thể vượt cả phương sai do việc chọn mẫu dữ liệu eval. Nói cách khác, nhiễu từ mô hình có thể lớn hơn nhiễu từ việc bạn chọn 200 câu nào.
Ví dụ: dựng harness chạy lặp
Quay lại tình huống thứ Năm. Thay vì chạy mỗi prompt một lần, bạn chạy mỗi prompt năm lần và ghi lại mọi thứ. Đoạn code dưới đây dùng OpenAI Python SDK; grade là hàm chấm của bạn, trả về 1 nếu đúng, 0 nếu sai.
import statistics
from collections import Counter
def run_eval(client, cases, model, n_runs=5):
scores, fingerprints = [], Counter()
for _ in range(n_runs):
correct = 0
for case in cases:
r = client.chat.completions.create(
model=model,
temperature=0,
seed=42,
messages=case["messages"],
max_tokens=case.get("max_tokens", 256),
)
# ghi fingerprint để biết backend có đổi giữa chừng không
fingerprints[r.system_fingerprint] += 1
correct += case["grade"](r.choices[0].message.content)
scores.append(correct / len(cases))
return {
"mean": statistics.mean(scores),
"min": min(scores),
"max": max(scores),
"fingerprints": dict(fingerprints),
}
Thử hình dung kết quả: prompt cũ dao động 79% đến 85%, prompt mới 78% đến 84%. Hai khoảng chồng lên nhau gần hết. Câu trả lời trung thực cho khách hàng là “chưa thấy khác biệt”, không phải “rollback”.
Bây giờ hình dung trường hợp ngược lại: prompt mới dao động 88% đến 91%, khoảng thấp nhất vẫn cao hơn khoảng cao nhất của prompt cũ. Lúc đó bạn có bằng chứng.
Vì sao không chạy một lần cho nhanh? Một nghiên cứu khác trên arXiv (2509.24086) cho thấy bảng xếp hạng dựa trên một lần chạy rất mong manh: 10 trên 12 lát cắt đảo ít nhất một cặp thứ hạng so với kết quả đa số của ba lần chạy. Nhóm tác giả khuyến nghị tối thiểu 2 lần lặp khi giải mã ngẫu nhiên.
So khoảng min-max từ năm lần chạy vẫn là quy tắc thô. Muốn chắc hơn, Indeed Engineering lưu ý rằng nhiều output cho mỗi input tạo ra dữ liệu dạng phân cụm, nên khi tính khoảng tin cậy hãy bootstrap theo input: lấy mẫu lại từng câu hỏi cùng toàn bộ các lần chạy của nó, thay vì trộn mọi output làm một.
Còn nếu cột fingerprints cho thấy hai giá trị khác nhau trong cùng một đợt chạy, hãy tách kết quả theo fingerprint trước khi kết luận, vì có thể bạn đang so hai backend chứ không phải hai prompt.
Tự làm trên dự án của bạn
Bắt đầu bằng việc đo nhiễu nền: chạy bộ eval hiện tại năm lần với cấu hình không đổi. Khoảng min-max bạn thu được là “sàn nhiễu”; mọi chênh lệch nhỏ hơn mức đó không nên được trình bày như một cải tiến.
Tiếp theo, ghi system_fingerprint, model, seed và max_tokens vào log của từng lần gọi. Khi khách hàng hỏi vì sao điểm tuần này khác tuần trước, bạn có dữ liệu để trả lời thay vì đoán.
Sau đó, xem lại cách chấm. Với output dài, so khớp chuỗi chính xác gần như chắc chắn sẽ vỡ; hãy yêu cầu mô hình trả về trường có cấu trúc rồi chấm trên trường đó, hoặc chấm theo tiêu chí thay vì theo từng chữ.
Những lỗi hay gặp nhất
Lỗi phổ biến nhất là so hai lần chạy đơn lẻ rồi kết luận. Với mức dao động tới 15% mà nghiên cứu arXiv 2408.04667 đo được, một chênh lệch 4 điểm từ một lần chạy không nói được gì.
Kế đến là tin rằng seed cộng temperature 0 bảo đảm tái lập. Chính nhà cung cấp chỉ hứa “cố gắng”, và tài liệu Azure nói rõ output vẫn có thể khác.
Lỗi khó thấy hơn cả là viết regression test dạng snapshot cho output dài. Test sẽ đỏ ngẫu nhiên, đội của khách hàng sẽ học cách bỏ qua nó, và đến khi có lỗi thật thì không ai để ý.
Kỹ năng này đáng ghi vào CV
Nếu trong buổi phỏng vấn có ai hỏi “bạn biết prompt mới tốt hơn bằng cách nào”, đừng dừng ở “điểm eval cao hơn”. Hãy trả lời bằng quy trình: chạy lặp nhiều lần, so từng case, cùng một fingerprint, báo khoảng dao động. Câu trả lời đó cho thấy bạn hiểu dữ liệu của mình.
Trên CV, một dòng như “dựng eval harness báo cáo độ dao động qua nhiều lần chạy và theo dõi thay đổi backend qua system_fingerprint” cụ thể hơn nhiều so với “có kinh nghiệm với LLM evaluation”. Khi đọc JD, nếu thấy nhắc tới eval hay reproducibility, hãy đưa đúng ví dụ này vào thư ứng tuyển và buổi phỏng vấn.
Khách hàng không cần một con số đẹp. Họ cần biết con số đó có còn đúng vào thứ Năm tuần sau hay không.
Bài này có hữu ích không?
Cảm ơn bạn đã góp ý!
7 nguồn
- Non-Determinism of "Deterministic" LLM Settings · 2024-08-06
- Defeating Nondeterminism in LLM Inference · 2025-09-10
- Defeating Nondeterminism in LLM Inference · 2025-09-11
- How to make your completions outputs reproducible with the new seed parameter
- How to generate reproducible output with Azure OpenAI in Microsoft Foundry Models (classic) · 2025-11-26
- Do Repetitions Matter? Strengthening Reliability in LLM Evaluations · 2025-09-28
- Bootstrap Confidence Intervals for LLM Evaluation · 2026-07-08