8.7.4. dasLLAMA-03 — Sampling
How the next token gets picked. SamplingParams carries the knobs, and its
defaults are greedy:
struct SamplingParams {
temp : float = 0.0 // <= 0 => greedy argmax; otherwise softmax temperature
top_k : int64 = 0l // <= 0 or >= vocab => no top-k cutoff
top_p : float = 1.0 // nucleus: keep the smallest probability mass >= top_p (>= 1 = off)
min_p : float = 0.0 // drop tokens under min_p * the top token's probability (<= 0 = off)
penalty : float = 1.0 // multiplicative repetition penalty over recent tokens (1.0 = none)
presence_penalty : float = 0.0 // subtracted from the logit of every distinct token in the window
frequency_penalty : float = 0.0 // subtracted once per occurrence in the window (OpenAI semantics)
penalty_last_n : int64 = 64l // penalty window: the most recent N generated tokens
}
Run it like tutorial 01:
daslang.exe -jit tutorials/dasLLAMA/03_sampling.das -- path/to/model.gguf
8.7.4.1. Greedy: deterministic, and loop-prone
Greedy argmax takes the single most likely token every step. Two runs are identical — but on small models the most likely continuation of a repetitive context is more repetition, so greedy text tends to loop. SmolLM2-135M demonstrates on cue:
greedy:, I was a young man, a young woman, and a young man again. I was a
young man, a young woman, and a young man again. ...
8.7.4.2. The repetition penalty
penalty > 1 scales down the logits of the last penalty_last_n
generated tokens before picking, so the argmax can’t keep choosing the same
phrase. presence_penalty and frequency_penalty do the same job by
subtraction instead of scaling — once for every distinct token in that window,
and once per occurrence — which is how the OpenAI API spells it. All three are
fully deterministic; no randomness involved:
greedy + penalty 1.3:, I was a young man with dreams of becoming an
engineer. My parents were both engineers and they encouraged my passion...
8.7.4.3. Temperature, top-k, and seeds
temp > 0 samples from the softmax distribution (higher = more
adventurous); top_k > 0 first cuts it to the k most likely tokens.
top_p and min_p cut by probability instead of by count — keep the
smallest set of tokens worth top_p of the mass, drop everything under
min_p of the top token’s probability. Sampling draws from the session’s
RNG, so variety comes from the seed — and set_seed makes any run exactly
reproducible:
def run_once(m : Model; prompt : array<int64>; params : SamplingParams; seed : int) : string {
var s = create_session(m) // fresh session, so runs compare cleanly
set_seed(s, seed)
var parts : array<string>
generate(m, s, prompt, params, 40l) $(_id, piece) {
parts |> push(piece)
return true
}
let out = join(parts, "")
delete s // six sessions in one run — free each (tutorial 04)
return out
}
let params = SamplingParams(temp = 0.8, top_k = 40l, penalty = 1.1)
let s7 = run_once(m, prompt, params, 7) // a story about Alex
let s8 = run_once(m, prompt, params, 8) // a story about Kanaq
let s7again = run_once(m, prompt, params, 7) // s7 again, token for token
See also
Full source: tutorials/dasLLAMA/03_sampling.das
Next tutorial: dasLLAMA-04 — Sessions, the KV Cache, and Memory