LLM Generation Parameters: Decoding Method Decides
Software Engineering · Generative AI

LLM Generation Parameters: Decoding Method Decides

9 September 2026

Thirteen knobs sit in a watsonx.ai generation config, and one of them silently decides whether the three most famous ones do anything at all — and it isn't temperature.

A model produces one token at a time: score the vocabulary, turn scores into probabilities, pick one, repeat. Every generation parameter changes how the pick is made, when the loop stops, or the plumbing around the call.

This piece is in two parts, and the line between them matters. Part 1 is what IBM documents — wording quoted from the API reference and SDK docs, with the ranges. Part 2 is my take — start values, traps, a symptom-to-fix table and four configs. Part 2 is opinion from use, not documentation; treat it as such.


Part 1 — Reference: what watsonx.ai documents

The gate: decoding_method

Value Documented behaviour What it enables
"greedy" Highest-probability token at every step. Deterministic by design. Nothing else in the randomness group. top_k, random_seed are documented as "sampling mode" only; temperature is "used to modify the next-token probabilities in sampling mode".
"sample" Draws the next token from the probability distribution. temperature, top_p, top_k, random_seed

Two consequences that follow from the documentation, not from opinion:

Observation Why
temperature: 0.9 with greedy decoding changes nothing The parameter is defined for sampling mode; greedy has no draw to shape.
temperature: 0 under sample gives the same answer every time Docs: "a value set to 0.00 would make it deterministic". IBM's own Prompt Lab material states that temperature 0 in sampling is equivalent to greedy.

Group 1 — Randomness (sampling mode only)

Parameter Documented behaviour (quoted) Range Notes
temperature "A value used to modify the next-token probabilities in sampling mode. Values less than 1.0 sharpen the probability distribution, resulting in 'less random' output." 0.00 – 2.00 0.00 = deterministic
top_p "The candidates to generate the next token are the most likely tokens with probabilities that add up to at least top_p." 0.0 – 1.0 1.0 = disabled
top_k "The number of highest probability vocabulary tokens to keep for top-k-filtering. Only applies for sampling mode." 1 – 100 top_k: 1 leaves a single candidate — behaves like greedy
random_seed "Random number generator seed to use in sampling mode for experimental repeatability." integer Same seed + same params + same prompt

Mechanism, computed — one step, three candidate tokens with logits 4.0 / 2.0 / 1.0, softmax after dividing by temperature (cut-off uses the "add until the sum reaches p" convention from the docs):

temperature P(A) P(B) P(C) top_p: 0.9 keeps top_k: 2 keeps chance C appears at least once in 300 tokens
0.2 1.000 0.000 0.000 A A, B ≈ 0
0.5 0.980 0.018 0.002 A A, B ≈ 52 %
1.0 0.844 0.114 0.042 A, B A, B ≈ 100 %
2.0 0.629 0.231 0.140 A, B, C A, B ≈ 100 %

Read across: temperature reshapes the distribution but removes nothing; top_p widens as the model gets less sure; top_k keeps a fixed count regardless. At temperature 0.5, top_p: 0.9 already leaves one candidate — the two parameters interact rather than stack.

Group 2 — Length

Parameter Documented behaviour (quoted) Default / limit
max_new_tokens "The maximum number of new tokens to be generated. The maximum supported value for this field depends on the model being used." model-dependent
min_new_tokens "The minimum number of new tokens to be generated. If stop sequences are given, they are ignored until minimum tokens are generated." —
stop_sequences "One or more strings which will cause the text generation to stop if/when they are produced as part of the output." list of strings
include_stop_sequence "Pass false to omit matched stop sequences from the end of the output text. The default is true." true — the matched stop text stays in the output
truncate_input_tokens "Represents the maximum number of input tokens accepted. Truncates the start of the input if exceeded." —
length_penalty "Can be used to exponentially increase the likelihood of the text generation terminating once a specified number of tokens have been generated." decay_factor: "factor of exponential decay; larger values correspond to more aggressive decay." start_index: "number of generated tokens after which this should take effect." object {decay_factor, start_index}

Group 3 — Repetition

Parameter Documented behaviour (quoted) Range
repetition_penalty "Represents the penalty for penalizing tokens that have already been generated or belong to the context. The value 1.0 means that there is no penalty." 1.0 – 2.0

Group 4 — Service

Parameter Documented behaviour (quoted) Notes
time_limit "Time limit in milliseconds — if not completed within this time, generation will stop. The text generated so far will be returned along with the TIME_LIMIT stop reason." ms
return_options input_text, generated_tokens, input_tokens, token_logprobs ("natural log of probability for each returned token"), token_ranks, top_n_tokens ("maximum value permitted is 5"). Logprobs, ranks and top-n are "applicable only if generated_tokens == true and/or input_tokens == true". diagnostics
prompt_variables Values substituted into a prompt template stored in watsonx. server-side templating

SDK note: GenTextParamsMetaNames.DECODING_METHOD is literally the string "decoding_method"; a plain dict with the same keys is accepted everywhere params is.

python
from ibm_watsonx_ai.foundation_models import ModelInference
from ibm_watsonx_ai.metanames import GenTextParamsMetaNames as GenParams
 
params = {
    GenParams.DECODING_METHOD: "greedy",
    GenParams.MAX_NEW_TOKENS: 300,
    GenParams.STOP_SEQUENCES: ["\n\n"],
}
model = ModelInference(model_id="ibm/granite-3-8b-instruct", params=params,
                       credentials=credentials, project_id=project_id)
print(model.generate_text("Extract the invoice total as a number only:\n" + document))

Part 2 — My take: how I set them

Everything below is judgement from use. Where it leans on a documented fact, the fact is in Part 1.

Decision rule

Task shape decoding_method Then
One correct answer — extraction, classification, JSON, code greedy Leave the randomness group unset; it's dead config here
Many acceptable answers — chat, drafting, brainstorming sample Tune temperature and top_p; leave top_k alone
A parameter appears to do nothing — Check the method before you check the model

Set decoding_method explicitly on every call. It costs one line and removes a whole class of "why is temperature doing nothing" questions.

One caveat on "deterministic": the docs describe greedy as deterministic, and it is by definition. Hosted inference has still shown run-to-run differences under greedy in the wild (there's a public langchain-ibm issue about it). For tests, assert on structure or with tolerance, not on byte equality.

Start values and traps

Parameter I start with Trap
temperature 0.7 chat · 1.0 ideas · 0.2 when I want near-greedy with a seed The tail is still reachable: at 0.5 a 0.2 % token shows up in roughly half of 300-token answers (see the computed table)
top_p 0.9 Interacts with temperature — at 0.5 it already leaves one candidate, so the output is deterministic again
top_k leave unset top_k: 1 is greedy in disguise; a fixed k ignores how sure the model is
random_seed any int, in tests and evals only Skip in production — two users shouldn't get identical answers
max_new_tokens 1.5–2× what the answer needs Doesn't make the model wrap up: the sentence stops where the ceiling lands. Shape the ending with stop_sequences
min_new_tokens unset The model can't stop (and per the docs, stop sequences are ignored) until N tokens — so it pads. A short answer is a prompt problem
truncate_input_tokens context − max_new_tokens − margin It cuts the start of the input (documented) — that's where the system instruction and few-shot examples usually are. Count tokens yourself for user-supplied input
length_penalty unset A soft length cap that only starts after start_index; max_new_tokens + stop_sequences give a hard, predictable one
include_stop_sequence false for structured output Default true means your parser sees the stop string at the end of every answer
repetition_penalty 1.0 for code/JSON · 1.05–1.15 for chat on smaller models It penalises every repeat — identifiers, JSON keys, the legal term that must appear twenty times. Past ~1.3 I've watched models rename a variable mid-function
time_limit set in production only Returns a partial answer with stop reason TIME_LIMIT — handle that reason, don't parse the fragment as complete
return_options generated_tokens + token_logprobs + token_ranks while debugging If the token you got was rank 1 at p ≈ 0.97, sampling isn't the problem — the prompt is. Turn off in prod
prompt_variables only if prompts live in the watsonx UI If you template in Python already, it's a second copy of the same machinery

stop_sequences recipes

Value Cuts at Use for Breaks when
["\n\n"] first blank line one-paragraph answers the answer is a bulleted list — the blank line between items is your stop token
["\n#"] next markdown heading model invents extra sections the answer legitimately contains headings
["```"] end of first code block code-only output model puts the fence before the code
your few-shot delimiter ("Q:") start of the next example few-shot prompts delimiter appears inside the answer
["}"] end of a JSON object single flat object nested objects — the first } closes the inner one

repetition_penalty ladder

Value What I see Use for
1.0 off (documented) structured output
1.05–1.15 gentle nudge chat answers on smaller models
1.2–1.3 noticeable greedy stuck in "designed to be designed to be…"
> 1.3 text quality drops —

Symptom → parameter

Symptom Cause Fix Not this
Identical answer on every call greedy, or temperature: 0 decoding_method: "sample", temperature: 0.7 raising top_k
temperature / top_p have no effect method is greedy decoding_method: "sample" changing the model
Stops mid-sentence max_new_tokens reached raise it, add stop_sequences for shape min_new_tokens
Keeps writing after the answer no stop condition stop_sequences lowering max_new_tokens
Only the first bullet comes back ["\n\n"] stop hits the list gap stop on ["\n\n\n"] or ["\n#"] removing stops entirely
Stop string shows up in the output include_stop_sequence default true set it to false trimming in the parser
Repeats one phrase in a loop greedy + small model repetition_penalty: 1.1, or sample repetition_penalty > 1.3
Renames variables mid-function repetition_penalty too high 1.0 for code/JSON prompting "don't rename"
Answers too short prompt ask for the shape you want min_new_tokens
Can't reproduce a bad output sample without seed random_seed in tests and evals greedy in prod
Instructions ignored on long inputs truncate_input_tokens cut the start count tokens; trim the document, not the prompt raising max_new_tokens
Partial answer, no error time_limit hit check the stop reason before parsing raising max_new_tokens

Four configs

Task decoding_method temperature top_p max_new_tokens stop_sequences repetition_penalty random_seed
Extraction / classification / JSON greedy — — 300 ["\n\n"] 1.0 —
Assistant answers sample 0.7 0.9 800 — 1.05 —
Idea generation sample 1.0 0.95 500 — 1.1 —
Evaluation runs sample 0.7 0.9 800 — 1.05 42
python
EXTRACT  = {"decoding_method": "greedy", "max_new_tokens": 300,
            "stop_sequences": ["\n\n"], "include_stop_sequence": False,
            "repetition_penalty": 1.0}
ASSIST   = {"decoding_method": "sample", "temperature": 0.7, "top_p": 0.9,
            "max_new_tokens": 800, "repetition_penalty": 1.05}
IDEATE   = {"decoding_method": "sample", "temperature": 1.0, "top_p": 0.95,
            "max_new_tokens": 500, "repetition_penalty": 1.1}
EVALUATE = {**ASSIST, "random_seed": 42}

Key takeaway: decoding_method isn't one of the thirteen parameters — it's the gate three of them have to pass through.