⚠️ Correction (2026-08-14)
An earlier version of this card reported 45.3% agreement with live human play and
claimed variants A and B were statistically identical. Both were wrong. vLLM
does not apply LoRA to Qwen3.5 (gated-delta-net linear attention; all-linear
targets those projections and vLLM skips what it cannot serve rather than
raising), so the evaluation was effectively scoring the base model. Three
differently-trained adapters returned byte-identical actions on all 161
decisions -- identical metrics across models was the bug signal.
Re-measured through PEFT, all 161 traces closing </think>:
Table with columns: model, agreement, EM, calls, below break-even| model | agreement | EM | calls | below break-even |
|---|
| v3_A (price floor, heuristic labels) | 82.0% | 75.8% | 59 | 1 |
| v1 A (heuristic labels) | 78.9% | 72.0% | 55 | 1 |
| v3_B (price floor, teacher labels) | 73.3% | 62.7% | 47 | 0 |
| v1 B (teacher labels) | 62.1% | 55.3% | 42 | 0 |
| 14B DPO adapter, for reference | 64.0% | 53.4% | 41 | 0 |
| what the humans actually played | -- | -- | 62 | 4 |
So the real findings are the opposite of what was published: reasoning beats
scale here (the best 4B leads a 14B DPO adapter by 18pp at a tenth the
parameters), and the label source matters a great deal -- heuristic labels
beat teacher labels by 16.8pp in v1 and 8.7pp in v3.
Serve this adapter with PEFT, not vLLM. Under vLLM it silently returns base
model behaviour.
Caveats: agreement with live human play is not GTO optimality, and there is no
bb/100 for this line yet.
PokerBench 8-max — Qwen3.5-4B reasoning (variant B, teacher labels)
What this is
A LoRA adapter on Qwen/Qwen3.5-4B trained to emit an explicit reasoning
trace before its action:
<think>Required equity = 3.5/(10.5+3.5) = 25%. I hold second pair with a
backdoor flush draw; against a range that bet flop and turn I have roughly
29% -- above the price, so calling is marginally +EV. Raising folds out
nothing that calls twice. Call.</think>
call
Reasoning traces were distilled from a Bedrock teacher (Claude Sonnet 5) in
the STaR style: the teacher was not told the answer, and a trace was kept
only if its conclusion matched the target label. That keeps every retained
trace one the teacher actually believed, rather than a rationalisation of a
conclusion handed to it.
⚠️ Not a deployment candidate
On the 161-decision DPT human-table regression set:
Table with columns: model, agreement with live play, exact match| model | agreement with live play | exact match |
|---|
pokerbench-qwen3-14b-lora-8max-dpo-potodds | 64.0% | 53.4% |
| this adapter | 45.3% | 31.7% |
Use the 14B DPO adapter for play. This one exists for the ablation below.
The finding
Variant A (heuristic labels, 4278 rows) and variant B (teacher labels, 6108
rows) came out statistically indistinguishable -- aggression 64 vs 65
decisions, agreement 46.6% vs 47.8%, pot-odds violations 5 vs 5 -- despite
42% different data volume and completely different label sources. So at this
scale the label source is not what moves the needle; the reasoning format is.
A second finding came out of the same run: both variants inherited a
pot-odds discipline gap (5 of 40 calls below break-even) directly from their
training data, where 13% of call rows were themselves below break-even (80 of
593 in A, 130 of 997 in B). The student reproduced almost exactly the data's
own violation rate. Later revisions enforce the arithmetic floor
mechanically rather than by prompting the teacher.
Usage
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-4B", torch_dtype="bfloat16")
model = PeftModel.from_pretrained(base, "ianlee1996/pokerbench-qwen35-4b-thinking-b-teacher")
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3.5-4B")
Sampling: temperature=0.1, top_p=0.95. Allow at least 512 output tokens --
the reasoning trace averages ~95 words and truncating it before </think>
yields no action at all.
Prompts must match the production renderer byte for byte; see
src/pokerbench/data/production_pe_renderer.py in the source repo.
Variant B specifically
Every usable trace is kept (6108) and the target is the teacher's own
decision, replacing the hand-written labeler. Higher ceiling in principle,
but it changes two variables at once, so it is only interpretable next to
variant A. Trace and label are consistent by construction here: the label
is the conclusion the reasoning argues for.
Compare with variant A,
which keeps the heuristic labels.