Skip to content

docs(transcripts): Qwen3-32B on vLLM, with and without thinking - #23

Merged
rokbenko merged 1 commit into
rokbenko:mainfrom
Vallhalen:transcripts/qwen3-32b-vllm
Sep 14, 2026
Merged

rokbenko merged 1 commit into
rokbenko:mainfrom
Vallhalen:transcripts/qwen3-32b-vllm

Conversation

@Vallhalen

Copy link
Copy Markdown
Contributor

What

Two runs of find-and-kick on the same build (739ff84), the same seed and the same server,
differing only in whether --extra-body turned the model's deliberation off. This is the
measurement asked for in #12 and #22: the tests there prove the object reaches the SDK call,
and nothing proved the thinking stops.

Kind of change

  • Docs only

What the two runs say

run steps LLM calls tokens in + out text fallbacks wall clock
thinking on (no flag) 4 8 35,416 + 2,049 0 195.9 s
thinking off (--extra-body) 3 5 21,802 + 263 0 29.6 s

Both succeeded on the same criterion. Three things the totals hide, all of them in the docs
change as well:

  • The runs took different paths. The thinking one spent two of its four steps on
    remember and quack. Part of 2,049 to 263 is a shorter route, not silence. The per-call
    figures are the honest ones: 165 to 402 output tokens a call becomes 19 to 130.
  • The first call stays expensive. 130 tokens and 16.0 s with thinking off, against 19 to
    40 tokens and 1.9 to 3.9 s for every call after it. It is not deliberation, because the
    quiet transcript has none anywhere.
  • reasoning_tokens reads 0 in both, which is a fact about a server running without
    --reasoning-parser rather than evidence of a model that did not reason. The thinking sits
    in content and is billed as output, which is why output tokens is the column to read.

The machine is an aarch64 NVIDIA GB10 (ASUS Ascent GX10) running vLLM 0.27.2.dev, which
nothing in this project had run on before.

Checklist

  • uv run ruff check . && uv run ruff format --check . && uv run mypy && uv run pytest pass locally
  • No network calls in tests; no API keys needed
  • No upstream assets added, from any project, and nothing out of ~/.quackd/cache
  • CHANGELOG.md updated under Unreleased
  • uv run quackd validate ducks/*.duck passes (unchanged by this PR)

One note on the transcripts themselves: duck_path and the memory path were rewritten to
relative form, matching the two transcripts already in the folder. The runs came from
uvx, so those fields held an absolute Windows cache path and a home directory. Nothing
else was touched.

Two runs of find-and-kick on the same build, the same seed and the same server,
differing only in whether --extra-body turned the model's deliberation off.

Both succeeded. With thinking on, all eight LLM calls deliberate in the open and
the run spends 2049 output tokens over four steps. With it off, none of the five
do and the run spends 263 over three. Neither needed the JSON text fallback.

The totals flatter it, because the two runs took different paths: the thinking
one spent two steps on remember and quack. The per-call figures are the honest
ones, 165 to 402 output tokens a call becoming 19 to 130.

reasoning_tokens reads 0 in both, which says something about the server rather
than the model. Without --reasoning-parser vLLM leaves the thinking inside
content, where it is billed as output.

The machine is an aarch64 NVIDIA GB10, which nothing here had run on before.
@rokbenko
rokbenko merged commit ee7a3c2 into rokbenko:main Sep 14, 2026
rokbenko added a commit that referenced this pull request Sep 14, 2026
Three documents and the plan counted the evidence for memory between
runs, and all four counted it as one model on one machine with no chain
anywhere. The two files this PR added make every one of those sentences
stale, and none of them is in a file the contributor touched.

README's Memory row and its Known limits bullet, and the same sentence
in docs/memory.md, now say two local models on two machines, and say
that one published pair carries a note from the run that saved it into
the prompt of the run after.

docs/memory.md's argument for putting `remember` inside the numbered
strategy rested on one model: Qwen 2.5 Coder 14B read the block and
never wrote to it. Qwen3-32B-AWQ now says the same thing from the other
end. With the call in strategy step 5 it made it, and the run after
correctly did not, because the fact was already in its prompt and the
Memory section says to skip one the prompt already remembers. That is
the feature working in both directions, which no transcript here had
shown.

PLAN.md's open item asked for two things, a second local server and a
run whose note is read by the next. #23 closed both. The item now asks
for Ollama, llama.cpp, a run on this machine, and a task harder than the
starter duck, which is what is actually left.

The README bullet the contributor did write now quotes the five shared
decisions rather than the totals, for the reason the previous commit
gives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rokbenko

Copy link
Copy Markdown
Owner

Merged. Your commit is on main as you wrote it, and the corrections are in eight commits after it, so you can see exactly what changed after you and why.

Every number in your tables came off run_end and matched: 4 steps, 8 calls and 35,416 + 2,049 against 3, 5 and 21,802 + 263, wall clock 195.9 s and 29.6 s, elapsed_s 6.7 in both. All thirteen llm rows carry stop_reason: tool_calls, so "no text fallback" is a count and not a hope. Eight of eight rows in the thinking run carry a thinking field and none of the five quiet ones do, which is the thing no test here could reach. quackd trace renders both files end to end.

The scrub held. No home directory, no drive letter, no host and no address in either file. One note on it: memory.path reads ~/.quackd/memory/..., which is not relative. It is the unexpanded default, and that is the right thing to publish, because memory_dir expands it at run time and the written form names nobody. I wrote "rewritten to relative form" in the row I added for your files and had to correct that as well.

Six things went in on top.

Your block landed between the Qwen 2.5 table and the four paragraphs that discuss it, so "These two are not a chain, and nothing here should be read as one" ended up sitting under your pair. That sentence is about seeds 5 and 6, and yours is a chain, so it said the opposite of the truth to anyone reading straight down. Both pairs sit whole now, one after the other.

The drop is 4.9x rather than 7.8x. You said the totals flatter it and then left the number at the ranges for each call. Five decisions are common to both runs, assess_task, search_scan, walk_to, kick and declare_success, and on those five it is 1,290 output tokens against 263. That is the comparison the page leads with now.

remember was not a step the quiet run saved. Its prompt already held the fact it would have written, and the duck says "Skip it if the prompt already remembers the same thing." It was obeying. quack is the one it actually missed, which strategy step 5 and the persona both ask for. Your thinking run made the opposite mistake: its first call asked for search_scan before recording a verdict, the gate refused it, and one of those eight calls bought nothing. Neither run followed the contract better than the other.

The expensive first call is the verdict. It is assess_task, and its reason argument is a five sentence paragraph, the longest single answer in the quiet run. Every call in that run decodes at 8.1 to 10.2 tokens a second, the first one included, so 130 tokens is about 13 of those 16 seconds.

"The thinking sits in content" is true of the wire and stops one step short for anybody opening the file. LocalProvider._normalise splits the <think> block into the row's own thinking field before anything else reads the text, which is why text is null on all thirteen rows. It runs there rather than later because the JSON fallback reads turn.text, and a model that weighs a verb inside its reasoning and then rejects it would otherwise have that call parsed out of a thought it had thrown away.

"One variable" is not quite it. The two system prompts differ by 397 characters, and that is exactly two lines.

Which is the part you did not claim. Those two lines are the note your thinking run saved and the episode quackd wrote from that same run. notes goes 1 to 2 and episodes 2 to 3, so nothing ran in between and the pair is provably adjacent. A note written by one run and read by the next, both ends published. PLAN.md has carried that as open since #7, and docs/local-llms.md said in as many words that no transcript here showed it. Yours is the first, and you sent it as the control half of a measurement of something else.

There is a test now. tests/test_transcript_assets.py reads every file in that folder, checks the table row against that file's own run_end down to the fallback count, checks the provenance row exists, sweeps the decoded strings for anything naming a machine, and holds the chain: the note has to stay in the other run's prompt and the counters have to stay one apart. Two PRs in a row have shipped a table of numbers that one person then checked by reading two files, and the second time that found a factor wrong by a good deal more than rounding. The wrong factor was mine and not yours, and so were three of the four things an adversarial pass over my own corrections turned up afterwards.

Still open here: Ollama, llama.cpp, anything at all on this machine, and any task harder than the starter duck.

Thanks for going and measuring it. You were told the tests could prove the object reaches the SDK call and nothing more, you had the only machine in reach that could settle the rest, and you built it as one run against another instead of two runs, which is the reason there is a number to argue about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants