Presence penalty và frequency penalty là hai tham số lấy mẫu (sampling) can thiệp thẳng vào điểm logit của mô hình để chặn hiện tượng lặp (degeneration), cái lỗi kinh điển mà bất kỳ ai từng tinh chỉnh suy luận của mô hình ngôn ngữ lớn cũng gặp ít nhất một lần. Bằng cách hạ xác suất của những token đã xuất hiện, hai tham số này kiểm soát entropy, tức độ đa dạng, của phân phối đầu ra, để mô hình không lặp lại chính nó mà vẫn giữ được cấu trúc ngữ pháp.
Nếu bạn làm việc với văn bản dài hoặc những tác vụ đòi hỏi sáng tạo, hiểu cơ chế của frequency penalty là điều kiện cần. Và theo những gì tôi quan sát được, đây cũng là cặp tham số bị nhầm lẫn với nhau nhiều nhất trong toàn bộ API của các nhà cung cấp mô hình: hai cái tên nghe gần như nhau, còn hai công thức thì khác nhau về bản chất.

Presence penalty và frequency penalty là gì?
Về mặt kỹ thuật, cả hai đều là tham số cộng (additive penalty) tác động thẳng vào logit, tức giá trị thô do mô hình tính ra trước khi đi qua tầng Softmax. Presence penalty phạt một token theo trạng thái nhị phân: token đó đã xuất hiện hay chưa, chỉ vậy thôi. Frequency penalty phạt theo mật độ, nghĩa là đếm xem token đó đã được sinh ra bao nhiêu lần.
| Tham số | Mục đích | Cách tính | Giá trị mặc định |
|---|---|---|---|
| Presence penalty | Khuyến khích mô hình mở sang chủ đề mới | Phạt một mức cố định cho bất kỳ token nào đã xuất hiện | 0 |
| Frequency penalty | Giảm các "tật" ngôn ngữ và lặp từ | Phạt lũy tiến theo số lần token đã xuất hiện | 0 |
Logic xử lý logit trong engine vLLM, viết bám đúng đặc tả của OpenAI, gọn đến mức này:
logits -= frequency_penalties * output_bin_counts
logits -= presence_penalties * output_maskTrong đó output_bin_counts là số lần token đó đã được sinh ra trong câu trả lời hiện tại, còn output_mask là giá trị nhị phân: 1 nếu token đã có mặt ít nhất một lần, 0 nếu chưa. Khác biệt nằm gọn trong hai dòng này: presence penalty là khoản thuế phẳng đánh một lần, còn frequency penalty tăng tuyến tính theo số lần lặp.
Cả hai đều nhận giá trị từ −2.0 đến 2.0 và mặc định bằng 0, tức là tắt. Giá trị dương làm giảm khả năng mô hình lặp lại token cũ. Giá trị âm thì ngược lại, nó khuyến khích mô hình lặp, và gần như không ai dùng trong môi trường thật.
Vì sao mô hình tự lặp lại chính nó
Lặp không phải là một lỗi lập trình, mà là hệ quả tự nhiên của kiến trúc tự hồi quy (autoregressive): trong không gian xác suất tồn tại những cái hố hút khiến bộ giải mã rất dễ rơi vào. Khi giải mã, mô hình chọn token có xác suất cao nhất ở mỗi bước. Nhưng một khi đã bắt đầu một cụm từ, chính sự mạch lạc cục bộ của cụm đó khiến nó tiếp tục là lựa chọn khả dĩ nhất. Tệ hơn nữa, xác suất của một cụm bị lặp còn tăng lên sau mỗi lần lặp, tạo thành vòng phản hồi khép kín mà bộ giải mã không tự thoát ra được.

Dữ liệu huấn luyện làm vòng lặp đó chặt thêm. Kho văn bản thu thập từ web dày đặc những cụm từ nghi thức và khuôn mẫu, kiểu "tóm lại", "do đó", "điều quan trọng cần lưu ý là". Mô hình tối ưu quá mức cho các khuôn mẫu này, vì chúng là bước chuyển an toàn về mặt thống kê. Nên khi mô hình mất mạch lập luận hoặc không còn đủ thông tin để viết tiếp, nó rơi về đúng những khuôn mẫu đó.
Prompt mơ hồ cũng buộc mô hình phải viết cho dài. Nếu đề bài không giới hạn phạm vi, mô hình có xu hướng diễn giải lại chính đoạn nó vừa viết để nghe cho có vẻ đầy đủ. Và khi không có ràng buộc nào về định dạng đầu ra, nó cũng không có cơ chế nội tại nào để tránh quay lại ý cũ.
Nặng nhất là kiểu sụp đổ độ đa dạng ở các tác vụ có cấu trúc. Với dấu gạch dọc trong bảng Markdown hay dấu ngoặc rỗng trong JSON, mô hình gán xác suất cực cao cho một chuỗi ký tự lặp, và phân phối xác suất co lại gần như hoàn toàn. Nếu không có can thiệp ở mức logit, bộ lấy mẫu không thoát nổi khỏi cực trị cục bộ đó. Đây cũng chính là chỗ mà một khoản phạt ở mức logit chứng minh được giá trị của nó rõ nhất, dù như bạn sẽ thấy ở dưới, nó cũng là chỗ dễ phạt quá tay nhất.
Hai tham số khác nhau ở đâu
Khác biệt nằm ở cách phân bổ khoản "thuế sử dụng lại" trên logit. Frequency penalty tỷ lệ thuận với số lần xuất hiện: nếu token "AI" xuất hiện lần thứ mười, nó bị trừ điểm nặng hơn nhiều so với hồi lần thứ hai. Cơ chế đó hiệu quả khi bạn muốn dọn sạch những từ đệm hoặc những tính từ bị lặp đi lặp lại.

Presence penalty lại là một khoản phạt phẳng. Một khi token đã xuất hiện, mức phạt là hằng số cho mọi lần sau, bất kể nó đã xuất hiện hai lần hay hai mươi lần. Nó được dùng để đẩy mô hình ra khỏi chủ đề hiện tại và đi tìm thông tin mới.
Đặt cạnh nhau thì rõ hơn: frequency penalty nhắm vào việc dùng lặp một từ cụ thể, tức là đa dạng từ vựng, còn presence penalty ngăn mô hình quẩn quanh một ý, tức là đa dạng chủ đề. Trong một báo cáo kỹ thuật, frequency penalty cao sẽ ngăn mô hình mở đầu mọi đoạn bằng cùng một từ nối. Presence penalty cao thì lại buộc mô hình bỏ hẳn chủ đề "hạ tầng đám mây" để chuyển sang "giao thức bảo mật" hay "quản lý chi phí", dù bạn có muốn nó chuyển hay không.
Dùng cả hai cùng lúc là được, nhưng phải cân. Frequency lo mật độ từ vựng trong phạm vi hẹp, trong khi presence lo độ rộng chủ đề. Nếu đẩy cả hai lên cao cùng lúc, bạn sẽ bỏ đói mô hình: nó không còn token hợp lệ nào để chọn, và thế là bạn gặp đúng những lỗi của việc phạt quá tay.
Repetition penalty: tham số thứ ba hay bị nhầm
Khác với cơ chế cộng của OpenAI, các mô hình mã nguồn mở thường dùng repetition_penalty dạng nhân. Tham số này hoạt động như một số chia áp lên các điểm logit dương: token nào đã xuất hiện thì logit của nó bị chia cho giá trị phạt.

Có hai khác biệt đáng nhớ. Thứ nhất, repetition_penalty áp dụng trên mặt nạ gộp cả token trong prompt lẫn token đã sinh ra, trong khi hai tham số kiểu OpenAI chỉ nhìn phần đã sinh. Thứ hai, vì là phép nhân nên nó nặng tay hơn hẳn: đặt quá cao, nó phá luôn cấu trúc ngữ pháp cơ bản, bởi nó phạt cả từ nối thiết yếu lẫn dấu câu mà không phân biệt nổi đâu là lặp bệnh lý, đâu là một dấu phẩy dùng đúng chỗ.
Trong thư viện Hugging Face Transformers, khoản phạt này chỉ áp dụng nhiều nhất một lần cho mỗi token, và với các mô hình chỉ có bộ giải mã (decoder-only) thì phạm vi xét mặc định bao gồm cả prompt. Bài báo CTRL năm 2019, nơi kỹ thuật này ra đời, đề xuất giá trị khoảng 1.2 để cân bằng giữa nội dung trung thực và việc không lặp.
Các hệ thống suy luận hiện nay phần lớn đã chuyển sang những phương án tinh hơn, chẳng hạn bộ lấy mẫu DRY. Khác với repetition penalty, DRY đánh giá cả chuỗi token thay vì từng token rời rạc. Nhờ phân tích thống kê n-gram và độ dài chuỗi trùng, DRY chặn được các vòng lặp thảm hoạ mà không giết mất phần ngữ pháp một từ vốn cần cho câu văn mạch lạc. Nếu hệ thống của bạn có sẵn DRY, tôi khuyên bạn dùng nó trước khi nghĩ đến chuyện vặn repetition_penalty lên.
Nên đặt giá trị bao nhiêu
Hãy tinh chỉnh theo từng bước 0.1, để không làm méo phân phối xác suất quá nhanh. Các mức tham khảo theo loại tác vụ:
| Loại nội dung | Frequency penalty | Presence penalty |
|---|---|---|
| Báo cáo dài, phân tích | 0.5 – 0.8 | 0.2 – 0.5 |
| Marketing, sáng tạo | 0.9 – 1.2 | 0.1 – 0.4 |
| Hỗ trợ khách hàng | 0.3 – 0.6 | 0.2 – 0.5 |
Với các mô hình mã nguồn mở dùng repetition_penalty dạng nhân, khoảng làm việc là 1.15 – 1.30.
Mức độ ảnh hưởng còn phụ thuộc vào cửa sổ phạt, trong llama.cpp là tham số repeat-last-n. Giá trị 0 tắt hẳn, một số dương giới hạn phạm vi nhìn lại đúng bấy nhiêu token, còn −1 quét toàn bộ cửa sổ ngữ cảnh. Đầu ra ngắn và có cấu trúc thì hợp với −1. Còn văn bản sáng tạo dài lại nên dùng một cửa sổ rộng nhưng có giới hạn, khoảng 256 đến 1024 token, để mô hình vẫn được phép dùng lại thứ từ vựng nó thực sự cần.
Có một chi tiết về thứ tự thi hành rất dễ bị bỏ qua: khoản phạt phải được áp trước các bộ cắt như Top-P, và đường ống mặc định làm đúng như vậy. Nếu đảo thứ tự lại, Top-P sẽ cắt kho token xuống còn mười, sau đó tám trong số đó bị phạt nặng, và mô hình buộc phải chọn giữa hai lựa chọn tệ còn lại.

Ví dụ một request kết hợp temperature với hai khoản phạt:
{
"model": "gpt-4o",
"messages": [{ "role": "user", "content": "Viết mô tả hệ thống..." }],
"temperature": 0.7,
"frequency_penalty": 0.5,
"presence_penalty": 0.2
}Khi phạt quá tay: ngữ pháp vỡ và từ bịa
Đặt penalty quá cao thì chính những từ tự nhiên nhất lại bị chặn trước tiên. Dấu câu, từ nối, các hư từ đều có tần suất cao trong mọi văn bản, nên chúng lãnh đủ sớm nhất. Kết quả là một đoạn văn không lặp nhưng đọc không nổi, vì mô hình đang dựng câu mà thiếu mất chất kết dính.

Hệ quả thứ hai là hiệu ứng từ điển đồng nghĩa. Khi bị phạt nặng vì lặp từ "cà phê", mô hình phải lách bằng những cách nói ngày càng kỳ quặc, kiểu "dung dịch đen chứa caffeine". Về mặt kỹ thuật, nó đã tránh được việc lặp, còn sự trong sáng của câu văn thì mất sạch.
Nghiêm trọng nhất là chu kỳ lặp thoái hoá. Một token bị lặp cho đến khi khoản phạt cộng dồn đủ lớn để cấm hẳn nó, rồi mô hình nhảy sang token khác và bắt đầu một vòng lặp mới y hệt. Nghiên cứu về LZ Penalty ghi lại một đoạn sinh ra từ QwQ-32B với frequency penalty 0.3 và temperature 0: một tràng dài "Non!", rồi một tràng "This!", rồi tới "Third!". Khoản phạt vẫn đang làm đúng việc của nó trên từng token, và đầu ra vẫn là rác.
Lý do nằm ở cấu trúc: cả frequency penalty lẫn repetition penalty đều chỉ nhìn thống kê đơn token, và không bao giờ quên. Mức phạt lên một token không hề suy giảm theo khoảng cách, nên trong một chuỗi suy luận dài, cuối cùng chúng cấm cả dấu chấm lẫn khoảng trắng. LZ Penalty được đề xuất để sửa đúng điểm này: mức phạt yếu dần theo khoảng cách, cho đến khi token rơi ra ngoài cửa sổ nhìn lại.
Vì sao các mô hình suy luận mới bỏ hẳn hai tham số này
Các dòng mô hình suy luận mới, gồm o-series và GPT-5, đã chính thức ngừng hỗ trợ presence_penalty và frequency_penalty. Cùng nằm trong danh sách không hỗ trợ còn có temperature, top_p, logprobs, top_logprobs, logit_bias và max_tokens.

Thay vì can thiệp thô vào logit từ bên ngoài, các mô hình này nội tại hoá việc kiểm soát lặp thông qua token suy luận, tức phần "suy nghĩ" không bao giờ hiện ra trong kết quả cuối. Mô hình tự nhận diện và bỏ các nhánh lặp ngay trong không gian suy luận nội bộ, nên khoản phạt ở mức logit trở thành thừa. Thứ bạn điều khiển bây giờ là reasoning_effort, với các giá trị none, minimal, low, medium, high, xhigh và max.
Việc kiểm soát lặp cũng dịch lên một tầng cao hơn. Trong các luồng chạy dài hoặc gọi nhiều công cụ, gpt-5.5 và gpt-5.4 cho phép gắn nhãn phase cho từng tin nhắn của trợ lý trong Responses API: commentary cho phần mô hình nói trước khi gọi công cụ, còn final_answer cho câu trả lời hoàn chỉnh. Tham số này không bắt buộc, nhưng nếu bỏ qua nó, mô hình có thể tưởng phần dạo đầu chính là câu trả lời cuối và dừng sớm.
Khi nào nên chạm vào chúng, khi nào không
Nguyên tắc của tôi là sửa prompt và ràng buộc định dạng đầu ra trước đã. Phần lớn vấn đề lặp được giải quyết bằng cách quy định rõ cấu trúc, đặt giới hạn số từ, hoặc liệt kê thẳng ra những cụm cần tránh. Một câu lệnh như "mỗi gạch đầu dòng phải nêu một khía cạnh kỹ thuật mới, không trùng nhau" thường hiệu quả hơn, và ít phá hoại hơn, so với việc đè trực tiếp lên logit.
Hai tham số này khuếch đại một prompt tốt chứ không thay thế được nó. Chúng đáng dùng khi bạn cần tinh chỉnh cảm giác của văn bản, hoặc khi cần phá một vòng lặp cứng đầu ở các tác vụ không phải suy luận. Còn trong đa số hệ thống chạy thật, để nguyên giá trị mặc định, hoặc chỉnh nhẹ trong khoảng 0.1 đến 0.3, vẫn là lựa chọn an toàn nhất.
Tài liệu tham khảo
- Utilities for Generation — Hugging Face
- Sampling Parameters — vLLM
- CTRL: A Conditional Transformer Language Model for Controllable Generation
- The Curious Case of Neural Text Degeneration
- LZ Penalty: An Information-Theoretic Repetition Penalty for Autoregressive Language Models
- Azure OpenAI Reasoning Models — GPT-5 Series, o3-mini, o1, o1-mini
- Sampling Args in llama-server — Alex Ewerlöf
- Stop the LLM From Rambling: Using Penalties to Control Repetition — DEV Community
- OpenAI API Specification