Instructions to use FoolDev/Thanatos-27B-HERETIC with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use FoolDev/Thanatos-27B-HERETIC with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M # Run inference directly in the terminal: llama cli -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M # Run inference directly in the terminal: llama cli -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Use Docker
docker model run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use FoolDev/Thanatos-27B-HERETIC with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "FoolDev/Thanatos-27B-HERETIC" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FoolDev/Thanatos-27B-HERETIC", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M
- Ollama
How to use FoolDev/Thanatos-27B-HERETIC with Ollama:
ollama run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M
- Unsloth Desktop
- Pi
How to use FoolDev/Thanatos-27B-HERETIC with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "FoolDev/Thanatos-27B-HERETIC:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use FoolDev/Thanatos-27B-HERETIC with Docker Model Runner:
docker model run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M
- Lemonade
How to use FoolDev/Thanatos-27B-HERETIC with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Run and chat with the model
lemonade run user.Thanatos-27B-HERETIC-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use FoolDev/Thanatos-27B-HERETIC with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use FoolDev/Thanatos-27B-HERETIC with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FoolDev/Thanatos-27B-HERETIC:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "FoolDev/Thanatos-27B-HERETIC:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download CHANGELOG.md from FoolDev/Thanatos-27B-HERETIC: direct link, hf CLI and curl.
- Browser
- Download file 223 kB
-
https://huggingface.co/FoolDev/Thanatos-27B-HERETIC/resolve/main/CHANGELOG.md
- Command line
-
hf download hf://FoolDev/Thanatos-27B-HERETIC/CHANGELOG.md
-
curl -L -o CHANGELOG.md https://huggingface.co/FoolDev/Thanatos-27B-HERETIC/resolve/main/CHANGELOG.md
Changelog
All notable changes to this repository. Format loosely follows
Keep a Changelog. The repository
holds a model card, an Ollama Modelfile, the HF Ollama-bridge template / system /
params files, tooling and the bundled Q4_K_M GGUF, so versions track the tooling
and documentation, not the underlying base model.
[Unreleased]
- Parked, needs a restamp:
chat_template.jinja:225still credits the reasoning field order to "the Janus sibling's order". The README half of that correction shipped in 0.12.5; this copy is stamped into the GGUF'stokenizer.chat_templateand pinned byverify_arch.py, so fixing a comment here costs a ~16 GB re-upload and a new blob sha256 that makes every existing puller re-download. Fold it into the next change that genuinely requires a restamp.
[0.12.17] — 2026-09-21
Notes
- A one-line correction to 0.12.16's own correction. Replacing 0.12.15's invented quotation
(below), 0.12.16 wrote that what 0.12.12 "did not exercise was the fetch-failure
direction" — naming one gap where there were two. 0.12.12 recorded exercising the in-sync
path, against the companion repo's identical-template
janustag, and the missing-tag path; the drifted direction was outside it as well. The sentence now says what that entry actually recorded. - That is the fourth consecutive release in which a correction carried a new inaccuracy. The common cause each time was writing the correction from the sibling repo's situation rather than reading this repo's own entry first.
Text-only release; blob untouched.
[0.12.16] — 2026-09-21
A third adversarial audit, run against 0.12.15, found that the two previous releases were generated from the sibling repo's text and carried its version numbers and its claims into this card. That transcription is amended in place — it is not a claim a reader could have acted on, it is this card printing the other card's words — and the false claims are corrected here in the usual way.
Fixed
- The "would span" correction landed in the script and not in the README. 0.12.15 declared that the header stated a hypothetical as past fact and fixed it; the identical sentence in the README — the actual model card — still read "spans". That is the third time in this sequence a fix landed in one file and not its pair.
- The drift hint's local branch dropped the tag it had just checked. Only the
hf.co/branch echoed${TAG}. Bothmakerefreshes now carryTAG=${TAG}, whichload_bundle.shandbuild.shboth honour. - The daemon probe answered a question it had not asked. It ran
ollama listonly to test reachability, then reported "not installed" for any otherollama showfailure. It now consults the list and reports one of three distinct causes. - The template guard merged three causes into one message — "missing, empty or unreadable" — the anti-pattern 0.12.14 fixed for the tag/daemon pair. Now one per message.
- The exit-2 enumeration went stale when 0.12.15 added the unreadable-template cause.
- The README named two live-daemon targets where the script header names three.
- 0.12.15's one-word "would span" edit left the header line at 84 columns, five past the wrap the rest of the block keeps.
Notes
- Corrections to the entries below:
- 0.12.15 "corrected" a sentence this repo never published. It quoted "0.12.12 verified only the matching direction" as a claim from the 0.12.13 entry. No such sentence has ever appeared here; it is the sibling's, about its own 0.9.12, and the supporting assertion about what 0.12.12 verified contradicted the 0.12.12 entry itself. Replaced with what is actually true of this repo.
- The 0.12.14 entry cited
0.9.13twice where it meant this repo's 0.12.13. Amended. - "seven were fixed in 0.12.13" (0.12.15). They were fixed in 0.12.14; 0.12.13 is the release the audit was run against. Amended.
- "the only user-facing script without one" (0.12.14). Still false:
strip_mtp.pyis documented for direct invocation and has no target either. Now a plain enumeration.
- Long lines introduced by scripted edits in the 0.12.14 and 0.12.15 entries (up to 385 columns) are reflowed to the file's width.
Text-only release; blob untouched.
[0.12.15] — 2026-09-21
Fixed
- An unreadable
templatewas reported as tag DRIFT. The guard checked that the file exists and is non-empty, then compared against$(cat "${TEMPLATE}")— which yields an empty string when the read fails on permissions. A readable tag plus an unreadable reference therefore compared unequal, and the script told the reader their tag had drifted and to rebuild it, when nothing was wrong with the tag at all. The guard now tests readability too, and the reference is read explicitly so a failed read can never be mistaken for drift. Verified with achmod 000template: exit 2, not 1. - The script header stated a hypothetical as past fact. It read "The equivalent gap here spans 0.12.7's tool-chain render …", describing a drift episode that happened on the companion Janus repo, not here. Now "would span".
Notes
- Unlike the sibling repo, no entry here ever claimed "0.12.12 verified only the matching
direction", so there is nothing of that kind to correct. What 0.12.12 recorded is
narrower than the sibling's: the in-sync path, exercised against the companion repo's
identical-template
janustag, and the missing-tag path. The drifted direction and the fetch-failure direction were both outside it. - This closes the findings from the second adversarial audit. Of the ten distinct defects it confirmed, seven were fixed in 0.12.14 and the remainder here.
Text-only release; blob untouched.
[0.12.14] — 2026-09-21
Fixed
- The drift hint printed the destructive command this repo's own README warns against.
0.12.13 added a README warning that a bare
ollama create thanatos-27b -f Modelfileswaps a Q3/Q5/Q6 build back to the bundled Q4_K_M — and then hadcheck_tag_sync.shprint exactly that command as its drift hint. The fix landed in the README only. The hint now namesmake load-bundle(path B) andmake build QUANT=<yours>(path C) and says not to use the bareollama create. - The drift hint named a hard-coded refresh tag, not the tag just checked. Running the
check on
hf.co/…:Q4_K_Mand being told to refresh a different tag leaves the tag you actually hold stale — the same class of defect 0.12.13 fixed in the README while leaving it in the script. The hint now echoes the tag it was given, and branches on whether that tag is anhf.co/pull or a local build. - "An unreadable tag and a stopped Ollama daemon no longer report the same cause" was
false. The previous release merged both into one message offering them as alternatives,
which is the opposite of distinguishing them. The script now probes
ollama listand reports exactly one: a missing tag, or an unreachable daemon. - A doubled
# #comment marker at line 9 of the script header, introduced by the 0.12.13 header rewrite. Harmless to bash and shellcheck — which is why nothing caught it — but visibly wrong in shipped text. - The rewritten subsection swallowed the blank line before the
###heading that follows it, leaving a heading butted against a paragraph.
Notes
- Two claims in the 0.12.13 entry below are corrected here rather than rewritten:
- "the only README-documented script in the repo without a make target." False:
check_bridge_sync.py,check_go_template.pyandstrip_mtp.pyare all documented in the files table and have no target either. The true statement is narrower — it was the only script in the check/verify family without one.check_bridge_sync.py,check_go_template.pyandstrip_mtp.pyhave no target either, andstrip_mtp.pyis documented for direct use, so "user-facing" did not narrow it correctly. - "
curl -s… exits 0 on a 404, so … both sides empty." A 404 from this host returns a 15-byte body, not nothing, so a 404 produces a false STALE, not a false "up to date". Only a connection or DNS failure yields the empty-vs-empty case. Both are caught bycurl -fsSplus the empty-side guard; the rationale given for one of them was wrong.
- "the only README-documented script in the repo without a make target." False:
- These defects were found by a second adversarial audit, run against 0.12.13 itself. Its verification pass was still resolving when this release was cut, so further corrections may follow.
Text-only release; blob untouched.
[0.12.13] — 2026-09-21
Fixed
- The trailing-newline explanation was wrong, and the
awkscrape it justified was never needed. 0.12.11 and 0.12.12 both said "this repo'stemplatehas no trailing newline andollama showadds one".ollama showadds nothing — the newline came from theawkin the scrape itself.ollama show --template <tag>returns the template verbatim, byte-identical to the file. The README snippet andscripts/check_tag_sync.shnow use--templateand compare directly; the scrape and the paragraph explaining it are gone. - The check could report "up to date" having compared nothing.
curl -sis silent on a DNS failure and exits 0 on a 404, so an unreachable reference plus an unreadable tag left both sides empty — and equal. The snippet now usescurl -fsSand refuses to compare when either side is empty. - The refresh advice named two commands for three documented paths, and one of them was
destructive. This card's Ollama section lists three paths (A
ollama run hf.co/…, Bmake load-bundle, Cmake build [QUANT=…]); 0.12.11 carried over the companion repo's two-path sentence. Worse, theollama create thanatos-27b -f Modelfileit named pointsFROMat the bundled Q4_K_M, so a reader who built Q3_K_M or Q5_K_M via path C would have silently swapped their quant back to Q4_K_M while "refreshing a template". All three refreshes are now named, with that substitution called out.
Changed
scripts/check_tag_sync.shhonours$TAGas well as a positional argument, matching its siblings, and gains amake check-tagtarget — it was the only README-documented script in the repo without one.- An unreadable tag and a stopped Ollama daemon no longer report the same cause.
Notes
- Claims in the 0.12.11 entry below are corrected here rather than rewritten, following
the sibling repo's precedent:
- "0.12.8's system-block fix … change how tool calls come out." 0.12.8 is a no-op
for any request carrying tools — both guards it adds are true when
.Toolsis non-empty. It changes requests with notoolsarray. Only 0.12.7 altered tool-call rendering. - "every check here passed … they all run against this repo's own files." The static
checks do;
make smoke,make smoke-toolsandmake benchrun against the installed tag on a live daemon. None of them compared its template with this repo's. - The "~1 s re-pull, no change in store size" figure was measured on the companion Janus repo's tag, not here — this host has never held a Thanatos tag. The content-addressing it illustrates applies equally, but the number is not this repo's and the 0.12.11 entry should not have stated it unattributed.
- "0.12.8's system-block fix … change how tool calls come out." 0.12.8 is a no-op
for any request carrying tools — both guards it adds are true when
- The
[Ollama](#ollama)link in the 0.12.11 entry pointed at an anchorCHANGELOG.mddoes not contain; it now resolves toREADME.md#ollama.
Text-only release; blob untouched.
[0.12.12] — 2026-09-21
Fixed
- 0.12.11's check could not be run by the readers most likely to need it. It ended in
$(cat template)and told the reader to run it "from the repo root" — but a reader who pulled withollama run hf.co/FoolDev/Thanatos-27B-HERETIChas no clone on disk at all. Exactly the people whose tag goes stale had nothing to compare against. The snippet now reads the template over HTTP from this repo (.../raw/main/template), so it works with nothing checked out. Verified against the live URL.
Added
scripts/check_tag_sync.sh— the same check as a script, for anyone who does have a clone. It completes the pair withcheck_bridge_sync.py: that one verifies theModelfileand the bridge files agree inside the repo, this one verifies an installed tag still agrees with the repo. Exit 0 in sync, 1 drifted, 2 unusable.
Notes
- The script splits
ollama showfrom theawkfilter rather than writingHAVE="$(ollama show ... 2>/dev/null | awk ...)"— the silent-exit footgun this repo shipped once and check.sh has guarded since. Underset -e+pipefaila missing tag aborts on the assignment and the emptiness check below never runs. - This host has no
thanatos-27btag, so the in-sync path was exercised against the identical-templatejanustag from the companion repo, and the missing-tag path against the real name. Both behaved as documented.
Text-only release; blob untouched.
[0.12.11] — 2026-09-21
Added
- The card never said that an Ollama tag does not track this repo. Ollama resolves a
tag once, at pull or create time, and never re-checks afterwards, so a tag made before a
template/system/paramschange keeps serving the old one indefinitely — with no warning, and withollama listshowing only the pull date. The two re-pull notes already on the card cover specific stale blobs; neither stated the general rule, and neither mentioned the chat template. New Keeping your tag current subsection under Ollama states it, gives a check, and names the refresh for each delivery path.
Notes
- Observed on the companion
FoolDev/Janus-35B-HERETICrepo, which ships an identical Gotemplate: a local tag there served a four-release-old copy for two days. The equivalent gap here would span 0.12.7's tool-chain render and 0.12.8's system-block fix — the last two releases that touchedtemplate— so a stale tag would emit an out-of-date tool format while every check in this repo passed against the repo's own files. - The check deliberately compares with
$(...)on both sides rather than piping intodifformd5sum. This repo'stemplatecarries no trailing newline andollama showadds one, so the naive form reports a mismatch that is not there — verified in both directions before publishing. - Refreshing is cheap when only the template moved: the weights layer is content-addressed, so an unchanged blob is not re-downloaded. A re-pull of an already-current tag was measured at ~1 s with no change in store size.
Text-only release; blob untouched.
[0.12.10] — 2026-09-19
Fixed
check.sh's pyflakes hint named a command that fails on the maintainer's own host. It saidpip install pyflakes, which a PEP 668 distro (Arch, Debian 12+, Fedora) refuses outright — and PEP 668's own error message then steers you topipx, which cannot satisfy this check: it runspython3 -m pyflakes, so pyflakes has to be importable by the system interpreter, and a venv or pipx install leaves the check skipping forever. The hint now says that, and gives both working routes: a--user --break-system-packagesinstall, or the distro package.- The card's
scripts/check.shrow listed the lint steps incompletely (it namedpyflakesorshellcheckbut not both) and did not mention that either one is skipped when absent, with the tail reporting the count rather than a green all-passed.
Notes
- With pyflakes present this repo now reports all 10 checks passed with nothing
skipped, and pyflakes finds no issues in any tracked python file. The
[~]skip path was still exercised for this release by hiding the user site directory (PYTHONNOUSERSITE=1), so the new hint is the text an affected reader actually sees. - One gotcha the hint's
--userroute carries, worth knowing before you pick it: it installs under~/.local/lib/python3.X/site-packages, which is scoped to that python version. A distro upgrade to a new python minor silently returns this check to "skipped". The distro package does not have that problem. - Text-only release; blob untouched, sha unchanged.
[0.12.9] — 2026-09-19
Second tier of the adversarial audit whose top tier shipped in 0.12.8. No template change, no blob change: this release is scripts and card accuracy.
Fixed (scripts that reported success while doing nothing, or failure while succeeding)
cap_ctx.shcould emit nonum_ctxoverride at all and still print[+] Done. baked at num_ctx=…. Its awk only rewrote an existingPARAMETER num_ctxline, with noENDfallback — so if the Modelfile ever lost that line, the script produced a Modelfile with no cap and reported success: the exact OOM it exists to prevent. It now appends the override when no line matched, and hard-fails if the patched Modelfile somehow carries none. (live_check.shhas had the correctENDrule since 0.6.4; this is the same shape.)fetch_vision.shprinted "download failed" after a successful download.hf download REPO FILE --local-dir DIRwrites toDIR/FILE: only the directory part ofMMPROJ_PATHwas honoured and the basename was discarded, so a custom filename fetched ~900 MB successfully and then failed its own existence check and exited 1 — and the re-run guard probed a path that was never created. It now moves the file into place.MMPROJ_PATHis also documented in the usage header for the first time; it was readable only from the source.verify_arch.pysaid "no GGUF found" about a file that is right there. In any clone made without git-lfs the bundle is a ~136-byte pointer; the size filter correctly refused it but the message diagnosed the wrong problem. It now recognises the pointer and namesgit lfs pull/load_bundle.sh. Exit code 2 is documented, and so is theggufdependency — previously the first thing a reader hit was a bareModuleNotFoundError.build.shandload_bundle.shdiscovered a missingggufpackage only after downloading the quant. Both now preflight their python imports before any download.check_bridge_sync.pysilently dropped any*_SYSTEMconstant that is not a literal.ast.literal_evalraises on an f-string or a"".join(...), and the bareexcept: continueturned that into a pass — a drifted system prompt written that way exited 0 with[ ok ] … in sync. It now prints a[~]line naming the constant it could not verify. Deliberately not a hard failure: this script is the pre-commit hook and a non-literal is legal, merely unverifiable.strip_mtp.pyraised a TypeError traceback instead of its own diagnostic when{arch}.block_countwas missing (thegeneral.architecturelookup two lines above already had that guard), and it dropped exactly one block regardless ofnextn_predict_layers— a model declaring two MTP heads would have been half-stripped and reported as a success. It now refuses. Both paths tested against a stubbed reader.live_check.sh'sollama create— the one call that writes ~21 GB into a model store — routed only via an ambientexport24 lines earlier. That export going missing is exactly what shipped in 0.6.1–0.6.3 and imported the blob into the user's own store; the 0.6.4 guard fires after create, so it can detect but not prevent. The call now carries an inlineOLLAMA_MODELS/OLLAMA_HOSTprefix, like the server launch above it.check.shpromised to ignore vendored directories and did not. Only.gitand__pycache__were excluded, so a repo-local.venvwould be fed to shellcheck andpy_compile, and therm -rfsweep would reach inside it. The exclusions.gitignorealready anticipates are now real.
Added (two more guards, both negative-tested)
- The default-tier check was scoped to the wrong slice. It counted
$effortassignments inside the{{ if $.ThinkLevel }}body only, so an assignment placed after that guard —{{ if not $.ThinkLevel }}{{ $effort = … }}, say — moved this repo's documented default tier while the check still printed[ ok ]. Verified: the old check passes that mutant, the new one fails it. - Nothing compared the tool-call tag advertised to the model with the tag Ollama
actually parses. The
<tools>preamble is prose to every other assertion here, so the system block could teach<function_call>while the parser scanned for<tool_call>and every tool call came back as plain text — 0.9.5's outage reached by a different route. The check now requires the derived tag to appear in the preamble.
Fixed (card claims that were not true of the shipped files)
- The "turn replay off" recipe told the reader to replace a line no file contains. It
quoted
{{ if (and $.IsThinkSet .Thinking) -}}; the shipped condition has been{{ if $.IsThinkSet -}}since 0.9.5. The replacement side was right; only the "from" side was stale, so anyone following it literally found nothing to change. - The replay section described pre-0.9.5 behaviour as current, saying every assistant
message that carries reasoning renders a block. The condition is
$.IsThinkSetalone, so every assistant message renders one — an empty<think>\n\n</think>when the turn did no reasoning. Three other places in the repo already said so. - There is no
xhighthink level. Ollama's levels arelow/medium/high/max;xhighandultraare clamped tomaxbefore any template runs, andapi.ThinkValue.IsValid()accepts only those four. "xhigh" is this repo's name for the instruction text thehigh/maxarm inserts. The card presented it as a level in a table column, which would lead anyone hand-editing the template to writeeq $.ThinkLevel "xhigh"— a condition that can never fire. - The template-selection rule was stated without its deciding clause. Ollama does not
simply prefer whichever template advertises more capabilities: a Go template that renders
a tool round trip is kept even when the embedded one advertises more, unless the embedded
one renders a round trip too (
shouldPreferChatTemplate,server/images.go). The card's conclusion was right; it reasoned from an incomplete rule to get there. ollama_chat.pypointed atOLLAMA_NEW_ENGINE=1, an environment variable that existed at v0.24 and is gone by v0.33.3. Thinking is separated from content with no flag at all: Ollama defaultsthinkto true for any model whose template advertises the capability, as this one's does.llama_cpp_vision.pyjustified its chat handler with a false fact. Qwen 2.5 VL and Qwen 3.x vision do not share a projector layout —qwen2.5vl_mergerandqwen3vl_mergerare distinct types and the qwen3vl family adds deepstack tensors. The script works because mtmd reads the projector type from the GGUF; the handler only supplies the ChatML wrapper. As written it invited the conclusion that a Qwen 2.5 VL mmproj is substitutable, which it is not.- The Modelfile quoted a thinking-block condition the template does not carry
(
(and $.IsThinkSet .Thinking)), which the Go-template guard explicitly forbids — the same stale quote the README carried, on a file the two repos otherwise keep in lockstep.
Recorded (how Ollama actually hands messages to a template)
- Ollama does NOT pass the message list to the template unmerged — the sibling's
changelog said so, citing
server/prompt.go, and this repo reasons about the same byte-identical template. Measuring it turned up something worse.collate(template/template.go, called fromExecutebefore the template runs) merges consecutive same-role messages;server/prompt.goonly truncates. Measured on Ollama 0.33.3 with its own renderer: two consecutive assistant turns arrive as one turn with the contents joined by a blank line — and when the second turn carriestool_callsandthinking, both are silently dropped, leaving no<tool_call>block at all. The operative conclusion still holds, and is now confirmed from the other direction:toolis the exempted role, so consecutive tool results really do reach the template separately, which is why the grouping this template does is necessary.
Notes
- Blob untouched: text-only release, sha unchanged.
- Still open from the audit, and deliberately not taken here: pinning the ChatML turn
delimiters outside the tool branch in
check_go_template.py(a real guard-strength gain against a real cost — every legitimate one-token edit to those branches becomes a check failure, and the last four releases were exactly such edits), and extendingverify_arch.pywithrope.freq_baseplus a YaRN-absence assertion, which can only be validated against the real blob.
[0.12.8] — 2026-09-18
Fixed (the system block no longer ends on a blank line)
- A request with no tools rendered
{system}\n\n<|im_end|>where Qwen renders{system}<|im_end|>. The\n\nafter{{ .System }}is the correct separator when the# Toolsblock follows it, but with no tools nothing consumed it; the same dangling pair followed the$effortline. Both separators are now conditional —{{ if or .System .Tools }}after$effort,{{ if .Tools }}after.System. This fired on the default path: the Modelfile bakes aSYSTEMand Ollama passes it as.System, so every request without atoolsarray carried the stray blank line. - Neither new condition names
.ToolCalls, so Ollama'sparseTagstill lands on the tool-call block — verified after the edit: derived tag<tool_call>, capabilitiescompletion tools thinkingread back withollama show.
Fixed (correcting 0.12.7's own render claim)
0.12.7 said "10 of 12 [shapes] now match byte for byte". That was body-only, and the sentence did not say so. The comparison behind it stripped the system block from both sides and masked the tool-call payload — each a documented deliberate difference, but unstated — and none of the twelve shapes carried a system message, which is precisely what hid the defect above. The matrix is now sixteen shapes, four of them system-carrying. The honest numbers, against this repo's own
chat_template.jinja:comparison 0.12.7 0.12.8 whole prompt, no-tools shapes 2 / 7 6 / 7 message body only (system block excluded) 14 / 16 14 / 16 The remaining miss in both columns is the one deliberate difference — with the last message an assistant turn, the Go path leaves it open for prefill. With tools in play the whole-prompt comparison never matches by design: the Go template's system block teaches JSON tool calls where the Jinja one teaches Qwen's XML form.
Fixed (top tier of an adversarial repo audit)
scripts/check.shrejected every commit after anymake build. Check 5 counted*.ggufon the filesystem, butbuild.shdefaults its download into the repo root and writes a*.stripped.ggufsibling beside it — three files, check fails, and withcheck.shwired into the pre-commit hook everygit commitwas refused. Both extras are gitignored, sogit statusstayed clean and gave no hint at all. The scan now drops paths git ignores; a file git does not ignore still counts, which is the case the check exists for, and the failure message now names what it found.examples/transformers_quickstart.pycould not produce a token. On transformers v5 — the only line that can load this base —apply_chat_templatehands back aBatchEncoding, so the v4-stylemodel.generate(inputs, …)raisedAttributeErroron.shape, as did the decode slice. It now passesreturn_dict=Trueexplicitly, callsgenerate(**inputs, …)and slices withinputs["input_ids"], which is correct on both v4 and v5.- The ROCm build recipe used
-DGGML_HIPBLAS=on, which ggml renamed toGGML_HIP. CMake reports an unknown-Donly as a trailing note after a full source build, so an AMD user waits out the build and gets a CPU-only wheel. Fixed inllama_cpp_quickstart.pyandllama_cpp_vision.py.
Fixed (a mechanism this card repeated in six places does not exist)
- There is no "Ollama Go engine", and no "C++ llama.cpp fallback" distinct from it. Since
ollama/ollama#16031 every GGML model is served by
the bundled llama-server subprocess: at v0.33.3 the source tree carries no vendored
llama/llama.cpp/and no Go model implementations at all —model/holds onlyparsers/andrenderers/(checked 2026-09-18). The card used that imaginary split twice: to explain why the bundled llama.cpp version does not affect text inference, and to explain why mmproj fails. In the Vision table it contradicted itself two lines apart — upstream has the arch entry, then the fallback lacks it. Corrected in the README, the loader table,examples/README.md,examples/llama_cpp_vision.pyandscripts/fetch_vision.sh. - The Ollama vision verdict is now
❓, not❌. The failure was observed and that observation stands; the cause given for it was wrong, and nothing has re-tested it since ollama/ollama#14575 closed on 2026-08-04. "The only working vision path right now" is gone.
Added
check_go_template.pypins the whole system block, not just the$effortemission: the.Systememission and both conditional separators are now part of the assertion. Deleting{{ if .System }}{{ .System }}used to be undetectable —live_check.shcompares token deltas that shift equally,smoke_test.shonly greps for leaked control tokens, andcheck_bridge_sync.pycompares the two copies to each other, so all three stayed green while every request lost its persona.
Notes
- Blob untouched: text-only release, sha unchanged.
- Provenance of the last three sections: an adversarial audit of the 0.12.7 tree — nine
independent passes, 66 raw findings, 41 surviving an independent refutation round. What
lands here is its top tier. Still outstanding and deliberately not in this release: stale doc
claims elsewhere in the card, four more guard gaps in
check_go_template.py,cap_ctx.shreporting success while emitting nonum_ctxoverride,fetch_vision.shreporting failure after a successful download, and defensive gaps instrip_mtp.pyandcheck_bridge_sync.py.
[0.12.7] — 2026-09-18
Fixed (the Go template renders a tool chain exactly as Qwen's template does)
Both remaining whitespace differences between the Go template and Qwen's format are gone,
and neither fix touches the range .ToolCalls body that 0.12.5 broke. Verified by rendering
twelve conversation shapes through Ollama's own renderer (/api/chat with
"_debug_render_only": true, 0.33.3, against a 1.2 MB stories260K.gguf in an isolated
CPU-only daemon) and diffing each against chat_template.jinja rendered by jinja2:
10 of 12 now match byte for byte, against 4 of 12 on 0.12.6. The two that still differ are
the one deliberate difference — with the last message an assistant turn, the Go path leaves it
open for prefill, with no <|im_end|>.
- The blank-line residue 0.12.6 documented is fixed. An assistant turn with tool calls and no
text rendered two blank lines after
</think>where Qwen renders one: the thinking block ended with a blank line and therange .ToolCallsbody adds its own leading newline on top. That blank line is now conditional —{{ if and $.IsThinkSet $blank }}— with$blankfalse exactly when the turn has tool calls and no text. - The blank line between an assistant turn's text and its first
<tool_call>is back, the alignment 0.12.5 attempted and 0.12.6 reverted. 0.12.6 recorded that it "cannot be had without placing an action exactly where the parser's prefix has to be". That is wrong, and this release is the counter-example: only the FIRST tool call needs the extra newline, and whether it is needed depends on.Contentalone — not on the loop index — so the separator can be emitted outside the range, where an action is harmless. The range body is still byte-identical to 0.12.4's.
Fixed (a second template-derived behaviour, found while fixing the first)
Both of these were found by reading Ollama v0.33.3's source and then reproducing them here, not by a render diff — neither is visible in rendered bytes.
- A condition that names
.ToolCallsbefore the tool-call block breaks tool calling just as thoroughly as an action inside it.parseTag(tools/template.go) searches for the FIRST{{ if }}whose pipe holds a.ToolCallsfield anywhere in the template, and reads the first literal text in THAT block; a new{{ if … .ToolCalls }}earlier in the file captures the search, yields no text, and the tag falls back to{, which is 0.12.5's failure exactly. The obvious way to write this release's fix walks straight into it. The template therefore aliases the field once —{{ $calls := .ToolCalls }}— and branches on the variable, which is a VariableNode and never matches that search. - Ending the thinking block with an
{{ if }}silently drops the thinking capability.InferTags(thinking/template.go) takes the first and last nodes of the list holding{{ .Thinking }}as the opening and closing tags, and both must be literal text. The first draft of this fix put the conditional blank line inside that block; Ollama then answered"does not support thinking"with HTTP 400 on any request that sets think. The conditional now sits after the block, gated on$.IsThinkSetso the no-thinking path renders as before.
Added
check_go_template.pyemulates both of Ollama's AST readers and asserts what they derive: the tool-call tag must come back<tool_call>, the thinking tags<think>/</think>. Ports ofparseTagandInferTagsfrom v0.33.3, with the Go-template lexing (including{{-/-}}trimming) and parsing they need. The port returns the known-correct verdict for all three templates whose live behaviour is on record:<tool_call>for 0.12.4 (2/2 first-turn calls parsed),{for 0.12.5 (0/5),<tool_call>for 0.12.6 (3/3). Negative-tested against three mutations — 0.12.5's template, a naive version of this release's fix that names.ToolCallsin the condition, and the first draft that ended the thinking block with an{{ if }}— which fail the tag check, both checks, and the tags check respectively.- These are the first checks here that read the template's parse tree instead of matching its
text. That matters because both failure modes are invisible to a render diff: 0.12.5's rendered
bytes were identical to 0.12.4's for every shape tested, so until now only a live tool call
against the bundled blob could catch them.
make checknow catches both in milliseconds.
Notes
- The blob is untouched — this is a text-only release, sha unchanged, no re-download.
- Not covered offline: that Ollama's tool-call parser behaves as the derived tag implies. The
derived tag and the advertised capabilities (
completion tools thinking, read back withollama show) are identical to 0.12.6's, which was measured live at 3/3 first-turn calls on the Janus sibling's blob, whose Go template is byte-identical to this one's, so the parser's configuration is unchanged by construction. Amake live-checkon the next Ollama upgrade stays the standing confirmation. - The Go
templateremains byte-identical to the Janus sibling's; both got this change.
[0.12.6] — 2026-09-18
Fixed (0.12.5 broke tool calling — upgrade straight past it)
- 0.12.5's tool-call separator silently disabled Ollama's tool-call parsing. Ollama does not parse
tool calls with a fixed grammar: it derives the parser from THIS template, taking the literal text at
the head of the
range .ToolCallsbody as the prefix to scan for in the model's output. 0.12.5 put an action there —{{- if $j }}, the blank-line separator — so the derived prefix no longer matched and Ollama stopped recognising tool calls at all. The model still emitted a perfectly correct<tool_call>block; it arrived as plain text incontentwithtool_callsempty. Measured on the real 35B-A3B blob of the Janus sibling, whose Go template is byte-identical to this one: 0/5 first-turn tool calls parsed on 0.12.5, 2/2 on 0.12.4, 3/3 after this fix, andscripts/live_check.shgoes from 16/17 back to 17/17. - The tool-call block is byte-identical to 0.12.4's again. The blank line 0.12.5 put between an
assistant turn's text and its first
<tool_call>is gone with it. That cosmetic alignment withchat_template.jinjais not worth breaking tool calls for, and it cannot be had without placing an action exactly where the parser's prefix has to be. Superseded by 0.12.7: it can — outside the range, keyed on a$callsalias — and both blank lines are back. - What 0.12.5 got right, and this release keeps: consecutive tool results still share one
<|im_start|>userturn, and reasoning still renders as<think>\n…\n</think>plus a blank line, including the empty block for a turn that did no reasoning. - Known cosmetic residue: an assistant turn that has tool calls and no text renders one extra blank
line after
</think>, because the range supplies its own leading newline where 0.12.5's construct did not.chat_template.jinjahas no such line. It costs one token and does not affect parsing. Fixed in 0.12.7.
Added
check_go_template.pypins the parser prefix: therange .ToolCallsbody must begin with literal text, never an action. This is the assertion that would have caught 0.12.5 offline; it replaces the separator assertion, which pinned the very construct that caused the bug. Worth recording why the existing guards all passed: the rendered BYTES were identical between 0.12.4 and 0.12.5 for every conversation shape tested, so no render diff could see it — the break was in the template's AST, which only a live tool call against a real model exposes.Also worth recording:
live_check.shnever sends two consecutive tool results, so it does not exercise the tool-response grouping this release keeps. That path is still render-tested only.This repo's own blob was not live-checked (it is a git-lfs pointer here and would need a 16.5 GB download); the
templatefile is byte-identical to Janus's, which was.
[0.12.5] — 2026-09-18
Changed (the Go template renders a tool chain the way Qwen's own template does)
Ported identically to the Janus sibling's 0.9.5 — the two repos' Go template files
stay byte-identical. Verified by rendering the same conversations through this repo's
chat_template.jinja and through Ollama's own renderer (/api/chat with
"_debug_render_only": true, 0.33.3) against a 1.2 MB stories260K.gguf in an
isolated CPU-only daemon, and diffing.
- Consecutive tool results now share one
<|im_start|>userturn, carrying one<tool_response>block each — whatchat_template.jinjahas always done on the llama.cpp path. The Go template used to emit a complete turn per tool result, so a parallel tool call rendered as two user turns on Ollama and one on llama.cpp. - A blank line separates an assistant turn's text from its first
<tool_call>; it was a single newline. - Replayed reasoning is wrapped as
<think>\n…\n</think>plus a blank line, and a turn that did no reasoning renders the empty<think>\n\n</think>block, matchingchat_template.jinjaexactly. The condition is$.IsThinkSet, not(and $.IsThinkSet .Thinking). - Nine test conversations now render identically on the two paths, modulo the two
deliberate differences this card documents: the tools preamble and the JSON-vs-XML
tool-call body.
ollama showstill reportstoolsandthinking. - Text-only. The blob sha256 is unchanged from 0.12.4 (
24a2d3a2…) — the Go template is not embedded in the GGUF, so nobody re-downloads. scripts/check_go_template.pygained assertions pinning the grouping, the separator and the new<think>wrapper.
Fixed
scripts/heretic_remote.shdefaultedMODELto this repo, which is GGUF-only — noconfig.json, no safetensors — sofrom_pretrainedcould never load it andSUBMIT=1 ./scripts/heretic_remote.shwith defaults would claim an a100-large slot and die. It now defaults to the vanilla baseQwen/Qwen3.8-27B, the model README "Provenance" names. This never worked: the default was self-referential from the commit that introduced it, not a casualty of a later rename.scripts/build.sh'sRequires:line omitted python3 and theggufpackage, although the script runsstrip_mtp.pyunconditionally — so the missing import surfaced only after a multi-GB download had already completed.scripts/check.sh's header enumerated 9 checks while 10 are implemented and the tail already printed "N of 10". The header also never said that a check whose tooling is missing is reported as skipped and never counted as a pass.
Documentation
- The sibling is Janus-35B again — the README badge and the
CITATION.cffsibling entry still said "Janus-27B", left over from the 0.8.x dense window. 0.12.3 corrected the body and missed both. - The "Janus vs Thanatos" section still described the dense twin. The
"Everything else is shared" paragraph listed this model's own shape — 64 layers,
hidden 5120, 24 Q / 4 KV heads, the
qwen35arch stamp — as shared with a model that has been a 40-layer MoE since 0.9.0. It now separates what the two genuinely share from what is this model's alone. - Janus's comparison figures are no longer "derived rather than measured" — they have been measured since its 0.9.2. Both sides of the table are now dated and sourced.
- The Modelfile still claimed the Jinja and Go paths disagree on an unset
reasoning_effort. 0.12.4 made both default to medium. The README's reasoning-effort prose carried the same stale claim from a llama.cpp run that predates 0.12.4, and is now scoped to what that run actually still shows. - The Modelfile's hardware block was still Janus's. Its provenance line claimed the same attention layout (64/16 layers vs 40/10), a 149.62 MiB recurrent state (Janus: 62.81) and a 0.7 GiB weight gap (really 4.37). Replaced with this repo's own 2026-09-18 measurements, including the compute buffer at 8K/32K/64K and the 5.08 tok/s run; the totals move to ~32.3 / ~24.8 GiB. Two README call-outs that still rounded to ~33 / ~25 GiB now agree with the card's own table.
make cap-ctxwas described as baking the pulledhf.cotag. It bakes a localthanatos-27btag from the bundled blob and this repo's Modelfile; the/v1caveat that motivates it was missing from one of the three copies.chat_template.jinjahas three deliberate changes from upstream, not two — the file itself and the README's other copy both said three.- The reasoning field-order credit is past-tensed. Janus ships no
chat_template.jinjasince its 0.9.0, so "the Janus sibling's order" now says when that was true.
[0.12.4] — 2026-09-18
Changed (the reasoning-effort default is medium on both paths)
chat_template.jinjanow resolves an unset or unrecognisedreasoning_efforttomedium, not upstream'sxhigh. The same request used to answer at a different tier depending on which template rendered it — xhigh through llama.cpp, medium through Ollama, where an unset think level adds no instruction at all. Both paths now default to medium; xhigh is something a client asks for explicitly.high,xhigh,maxandultrastill map to xhigh,low/minimalto low,nonestill switches thinking off. The Gotemplateneeded no change — it already behaved this way, and it is byte-identical to the Janus sibling's.- The GGUF is restamped so the embedded copy matches:
strip_mtp.py --chat-templaterewrote the metadata only — 45 KV keys copied, 851 tensors kept, 0 dropped, no re-quantization — andverify_arch.pyconfirms all 15 Architecture claims plus "Embedded chat template == chat_template.jinja (17751 chars, identical)". The blob has a new sha256 (24a2d3a21b5a4011d22acc7deb032fe252999dfec08c261409c71f8fa5b8e896), so an existing puller will fetch it again on the nextollama pull. - Nine new assertions pin the new default in
check_chat_template.py: unset, explicitnull, andauto/""/banana/5/Trueall resolve to medium. Nothing pinned it before — the existing tier assertions only covered values a client names explicitly, so the default could have regressed silently.
Measured (on this repo's own blob, not the sibling's)
- The Hardware section is this model's own measurement. Earlier releases carried
figures measured on Janus while it shared this architecture. Measured 2026-09-18,
Ollama 0.33.3 CPU backend, isolated store, at
num_ctx8192 / 32768 / 65536 plus a q8_0 pass: weights 15.40 GiB (CPU model buffer4935.19 MiB +CPU_REPACK10835.16 MiB), recurrent state 149.62 MiB (constant), KV 65,536 B/token at f16 (512 / 2048 / 4096 MiB) and 34,816 B/token at q8_0 (1088 / 2176 MiB), compute buffer 280.04 / 328.04 / 784.07 MiB. llama.cpp logs the cache as "16 layers". - The borrowed figures were accurate — 15.4 GiB, 0.15 GiB, 2 GiB and 1.06 GiB per 32K all held — so this is a provenance and precision fix: the compute buffer was a "0.3-0.8 GiB" range and is now three exact values, and the totals tighten to 17.9 GiB at 32768 and 20.3 GiB at 65536 (sums of measured parts), ~32.3 GiB at the 262144 default and ~24.8 GiB with q8_0.
- Throughput re-measured: 5.08 tok/s aggregate (1,852 tokens / 364,560 ms; 5.32 / 5.10 / 5.07), against 5.01 from the 0.12.0-era run. For scale, the Janus sibling's MoE measured 24.97 tok/s on the same CPU the same day — 4.9x, and that gap is now measured on both sides rather than inferred from active-parameter counts.
Added (check harness)
scripts/check_go_template.pypins the Go template's reasoning-effort mapping — the xhigh and low arms with their instruction strings, and the medium/unset default on this path. Negative-tested against mutated templates; every mutant fails. It extends check 9 rather than adding one, so the count is unchanged.- The check descriptions in
check.shand the README file table now name the effort mapping the guard covers.
[0.12.3] — 2026-09-18
Fixed (the sibling comparison described a Janus that no longer exists)
- Janus went back to the Qwen 3.6 35B-A3B MoE in its 0.9.0, hours after 0.12.1 recorded it as the dense Qwen 3.8 27B. Every claim here that the two are "the same model on paper", that "neither is a mixture-of-experts", or that Janus offers no capacity argument was false the moment that landed. The Janus vs Thanatos section is rewritten around what actually differs now: dense 27B vs 35B-A3B MoE, 64 layers vs 40, no experts vs 256 (8 routed + 1 shared), ~15.4 GiB resident vs ~19.78 GiB, and — the interesting inversion — 2 GiB per 32K of KV here against 0.625 GiB there, because the MoE has 10 full-attention layers to this model's 16. Janus's figures are marked as derived from its GGUF geometry; this card's remain measured.
- Corrected the same assumption everywhere else it had leaked: the intro's "same dense base, differing only in abliteration", the vocab note, the memory-section comparison, the GPU-offload attribution, the duplicate-tool-call note (which was measured when Janus was this base, and now says so), the dense-footprint limitation, and the Related models row.
- Nothing about this model changed. Thanatos has been the dense Qwen 3.8 27B throughout and did not move; only the statements about the sibling were wrong.
[0.12.2] — 2026-09-18
Fixed (heretic_remote.sh submitted against the wrong hf)
- On Arch,
/usr/bin/hfis an unrelated hidden-file utility, not the Hugging Face CLI, and the script ended in a bareexec hf jobs run …. On this machine that resolves to a binary with nojobssubcommand, so a submit would fail in a way that reads like an auth problem. The script now takesHF_BIN(defaulthf), refuses to submit when the resolved binary has nohf jobssubcommand, and prints the venv recipe in the error:python3 -m venv ~/.cache/hf-cli-venv && ~/.cache/hf-cli-venv/bin/pip install -q huggingface_hub, thenHF_BIN=~/.cache/hf-cli-venv/bin/hf SUBMIT=1 ./scripts/heretic_remote.sh. The preflight only runs on a real submit, so dry-runs still work anywhere.
[0.12.1] — 2026-09-18
Documentation (parity with the Janus sibling's 0.6.5-0.8.0 work)
- The Ollama-derived claims were pinned to v0.33.3 and v0.34.0. Ollama has since
shipped 0.34.1 (2026-09-14) and 0.34.2 (2026-09-15). Compared in source on
2026-09-17:
openai/openai.go,server/prompt.goandtemplate/template.goare byte-identical from v0.33.3 through v0.34.2, so the reasoning-replay field table, the/v1reasoning-effort mapping and the template-selection behaviour all still hold.server/images.gowas refactored but its selection conditions were not: a built-in renderer still displaces both templates only when the model's config names a renderer or parser, which this repo's does not — so Ollama still runs the Go template. Nothing was re-tested live; the live figures keep their original attribution. - Which llama.cpp an Ollama carries is now stated in Chat template: 0.33.3 and 0.34.0 bundle b10760, 0.34.1 bundles b10864 and 0.34.2 b10969. From 0.34.1 that is a b10763-or-later llama-server, which changes nothing here because text inference runs Ollama's Go engine.
- The loader table's
Ollama 0.24+floor was undated and unsourced. Replaced with the three versions actually checked in source, in the card,examples/README.mdandexamples/llama_cpp_vision.py.
Fixed
- Front-matter routed the repo wrongly.
library_name: transformersandpipeline_tag: image-text-to-texton a repo that ships noconfig.json, tokenizer or safetensors — HF offered a Transformers snippet that cannot resolve. Nowlibrary_name: ggufandpipeline_tag: text-generation.
Added (check harness, ported from Janus)
scripts/check.shcounts skipped checks and reports "N of 10 checks passed, K skipped" instead of a green "all checks passed" over a check that never ran (pyflakesis absent on the maintainer's host, so this was happening).- New check 10: the Modelfile's
FROMmust name the bundled GGUF and that file must be tracked by git. An untracked bundle looks fine locally and publishes a repo with no weights — which is exactly what it caught on the Janus side.
Note on the sibling
- Janus restored its 35B-A3B MoE in 0.7.0 on 2026-09-18 and moved back to the dense Qwen 3.8 27B in 0.8.0 the same day, so its CHANGELOG shows a round trip. The Janus vs Thanatos section records it; anyone who pulled Janus during that window has the MoE and should re-pull. Thanatos is unaffected — its base has not changed.
[0.12.0] - 2026-09-14
Changed (earlier reasoning is read from reasoning and thinking too)
chat_template.jinjareads an assistant turn's reasoning fromreasoning, thenreasoning_content, thenthinking, the Janus sibling's order; upstream's template read onlyreasoning_content. A non-string value is stringified, and an explicit null falls through to the next field. This is the template's third change markedCHANGED FROM UPSTREAM.- Why: loaders that hand the template the client's messages unchanged pass
whichever field the client used. KoboldCpp does (its
format_jinjainkoboldcpp.py,concedo4ac5721, only adjustscontentandtool_calls), and current vLLM / OpenRouter-style clients sendreasoning. Rendering both templates with KoboldCpp's own Jinja setup, a trace sent asreasoningorthinkingbecame an empty<think>block under the old template and is replayed under the new one. - Not affected: llama-server reads only
reasoning_contentfrom/v1messages before the template runs, so its behaviour is unchanged (masterf3a184b, b10760 and b10519:reasoning_contentreplayed, areasoningfield ignored, as before). LM Studio 0.4.24 normalisesreasoningtoreasoning_contentand dropsthinkingfirst, so the old and new templates rendered the same there (tested on its CPU runtime with a 260K-parameter model). Ollama runs the Go template. - README: the replay table gains LM Studio and KoboldCpp rows and a note on the field order; the file table and the list of template changes follow. The Reasoning effort section notes that the unset / unrecognised default is xhigh here and medium on Janus.
- The bundled GGUF is restamped with the new template -
tokenizer.chat_templateonly; all 851 tensors and every other metadata key compared byte-identical: sha256534fd0a4af4d26e3f6e5c3db5190d327138bda3802f11519b277c04422709158(was1e3249a2dd6512dff463e44a07c7ad165d833f28a2015af98e35f2013ef0502f). An Ollama user re-pullinghf.co/FoolDev/Thanatos-27B-HERETICdownloads the ~17 GB blob again. - Verified, CPU only. On llama-server master
f3a184b, b10760 and b10519 with--chat-template-file, a string-argument tool replay rendered XML andreasoning_effort: "max"and a tools request both returned HTTP 200. On master with the restamped blob, a real first turn atreasoning_effort: "low"thought briefly and returned one parsedget_weathercall, and the replay carrying thatreasoning_contentreturned HTTP 200 with the right answer. scripts/live_check.shon Ollama 0.33.3 passed all 17 checks on the restamped blob, withollama liston the default server unchanged: Ollama still selects the Go template, a first-turn call comes back once, a replayed string-argument tool result is answered, and reasoning replay and the effort tiers are unchanged (232 / 187 and 375 / 330 prompt tokens, as in 0.11.0).
Fixed (live checks talked to the wrong Ollama)
scripts/live_check.shin 0.11.1 and 0.11.2 did not use its isolated server. The CPU-only guard added in 0.11.1 replaced the lineexport OLLAMA_HOST="127.0.0.1:${PORT}"instead of keeping it, soollama createimported the GGUF into whatever Ollama answered on the default127.0.0.1:11434- normally your own store - and every check then queried the isolated server, which had no model, and failed with HTTP 404. No model was loaded or run by those checks, but a run could leave alive-check-thanatos-27b-hereticmodel in your own store: remove it withollama rm live-check-thanatos-27b-heretic. The export is back where it was before 0.11.1, and the script now also exits before any check if the imported model is not on the isolated server. Found when this release's first live run failed 17 of 17 with 404s; the rerun above is with the fix.
Changed (checks)
scripts/check_chat_template.pyasserts that reasoning inreasoningand inthinkingis replayed, thatreasoningwins overreasoning_content, and that an explicit nullreasoningfalls through: 52 assertions, up from 48. The first three fail against the 0.11.2 template.
[0.11.2] - 2026-09-14
Documentation (LM Studio tested; q8_0 cache command)
- The LM Studio app handles string tool-call arguments. Tested through LM Studio
0.4.24's
/v1/chat/completionson its CPU runtime (avx2 2.37.0), with a 260K-parameter model carrying this repo's template: a replayed call whoseargumentswas the string{"city": "Paris"}returned HTTP 200, and LM Studio's log shows it rendered as<function=get_weather><parameter=city>, so the app parses the arguments before its own Jinja engine renders.argumentsthat are not valid JSON return HTTP 500 before the template runs, with or withouttool_call_format: "auto". The Tool / function calling section replaces 0.11.1's "unconfirmed" note with this; KoboldCpp, Jan and LM Studio are now all covered. - The context note shows how to halve the KV cache:
OLLAMA_KV_CACHE_TYPE=q8_0 OLLAMA_FLASH_ATTENTION=1 ollama serve, or the same asEnvironment=lines viasudo systemctl edit ollama. That is what brings the 262144 default within reach of a 32 GB host.
[0.11.1] - 2026-09-14
Fixed (memory figures measured, not extrapolated)
- The context-memory numbers were wrong. The
Modelfilenotes and README said "2 GB per 32K (q8_0)", "22 GB weights + compute" and "~38 GB at 262144" (the last added in 0.11.0). Measured on Ollama 0.33.3's CPU backend from llama.cpp's own allocation lines, atnum_ctx8192, 32768 and 65536: Measured on the Janus sibling, which has the same attention layout; Thanatos's weights are 0.7 GiB smaller, and its totals below account for that.- The KV cache covers the 16 full-attention layers (4 KV heads x 256 dims) and is exactly 2 GiB per 32K tokens at f16, Ollama's default, and 1.06 GiB per 32K at q8_0. The old figure was the f16 number labelled q8_0.
- Weights take ~15.4 GiB; the 48 linear-attention layers add a fixed 0.15 GiB; the
compute buffer was 0.3-0.8 GiB with flash attention, which Ollama's default
autoenabled. Forcing flash attention off grows it to ~25 GiB at 262144 (llama.cpp master'sllama-fit-paramsprojection). - Totals at the 262144 default: ~33 GiB with the f16 cache, ~25 GiB
with
OLLAMA_KV_CACHE_TYPE=q8_0and flash attention; at 65536:20 GiB f16, ~18.5 GiB q8_0. Figures at 262144 and above add the fit-params compute-buffer projection (1-1.5 GiB), which was ~0.35 GiB above what Ollama allocated at 65536; the default was not load-tested because the test host has 27 GB. - So a 32 GB host can run the default with the q8_0 cache (tight), which the
README and
Modelfilehardware rows now say, alongside anum_ctx/ cache-type table.
Fixed (live checks refuse to use a GPU)
scripts/live_check.shnow runs Ollama on CPU only. It starts the isolated server withOLLAMA_LLM_LIBRARY=cpu,OLLAMA_VULKAN=0and the CUDA / ROCm / Vulkan device lists emptied, waits for Ollama's "inference compute" log line, and exits before importing if any device is notlibrary=cpu. On a host whose iGPU also drives the desktop, GPU compute had previously taken the session down; until now the script relied on the caller's environment. Tested against the tiny test model (it reported "CPU only: 1 CPU device(s)") and against a log with a Vulkan device line (refused).
Documentation (other loaders; a correction)
- Tool / function calling now covers KoboldCpp, Jan and the LM Studio app,
checked 2026-09-14. KoboldCpp v1.120 with
--jinja --jinja_toolsparses JSON-stringargumentswithjson.loadsbefore rendering; with this template a string-argument replay returned HTTP 200 and rendered XML (tested). Jan's v0.8.4 and main branch run llama.cpp's own server code (janhq/llama.cpp b9967, ggml-org b10809), which converts string arguments (from source). The LM Studio app renders templates with its own JS engine; its bug tracker suggests arguments reach templates as mappings, unconfirmed here. The README gives the workaround if one raises. - Correction to 0.7.0. Its tool-calling entry, and the matching comments
in
scripts/build.shandscripts/strip_mtp.pyuntil 0.10.0, said upstream's XML default makes llama-server return HTTP 500 on the second turn of every tool call. That was reproduced by rendering the template directly, not through llama-server, which converts string arguments into mappings before rendering; the raise only reaches loaders that pass the raw string through. The entry is left as written. - Tool calls with thinking on also work on the embedded template: on
llama-server master
97e4ca7with default reasoning effort, the Janus blob thought briefly, returned one parsedget_weathercall, and answered the string-argument replay with HTTP 200.
[0.11.0] - 2026-09-14
Changed (Ollama loads the full native context by default)
- The default
num_ctxis 262144, up from 65536, in theModelfileand the HF bridgeparamsfile, soollama run hf.co/FoolDev/Thanatos-27B-HERETICand a localollama createboth load the full 262,144-token native window. The GGUF already declares that window (context_length262144) and is unchanged; llama.cpp and LM Studio pick their own context size, so only Ollama is affected. - It needs more memory. Per this repo's ~2 GB per 32K estimate with a q8_0 KV
cache, the KV cache grows from ~4 GB to ~16 GB and the total from ~26 GB to
~38 GB; an f16 cache roughly doubles the KV. A 32 GB host no longer fits the
default: lower
num_ctxper session (/set parameter num_ctx 65536), passoptions.num_ctxon/api/chat, or bake a smaller tag withmake cap-ctxfor OpenAI/v1clients, which cannot override it. - Docs follow: the
Modelfilehardware notes and working configurations, the README context and memory notes and the hardware table, and thescripts/cap_ctx.shcomment now describe the 262144 default. The loader and hardware tables' RTX 5090 and Mac Studio rows now say they need a lowernum_ctxat 32 GB. - Checked:
scripts/check.shpasses, including theModelfile/ bridge-file sync check, and an isolated Ollama 0.33.3ollama createfrom thisModelfilereportsnum_ctx 262144. Not load-tested at the new default: that needs ~38 GB and the test host has 27 GB.
[0.10.0] - 2026-09-14
Changed (llama.cpp tool calls default to XML again)
chat_template.jinjadefaultstool_call_formattoxml, the only form upstream's template has; 0.7.0 to 0.9.0 usedauto. llama-server recognises this template as Qwen3-Coder-style (it contains<tool_call>,<function=and<parameter=) and parses the model's reply with a parser, and a lazy grammar triggered on<tool_call>, that accept only<function=NAME><parameter=ARG>- per llama.cpp'scommon/chat.cppandcommon/parsers/qwen3-coder.cppat master97e4ca7, with the same parser in b10760 and b10519. A prompt teaching the JSON form would get replies that parser cannot read, so pinning XML keeps prompt and parser in agreement.autoandjsonstay available as opt-ins through--chat-template-kwargsor a request'schat_template_kwargs, for loaders that hand the template JSON-string arguments. On llama-serverautorenders XML anyway, andjsonwould break parsing.- The second-turn 500 that motivated
autodoes not happen on llama-server. The template's capability probe shows it reads object arguments, so llama-server decodes a client's JSON-stringfunction.argumentsinto a mapping before rendering. Tested on 2026-09-14 with a 260K-parameter test model and--chat-template-file, replaying a call whoseargumentswas the string{"city": "Paris"}:/apply-templateand/v1/chat/completionsreturned HTTP 200 with the call rendered as<parameter=city>on master97e4ca7, b10760 (the llama-server Ollama 0.33.3 bundles) and b10519 (LM Studio's avx2 CPU runtime 2.30.0), underxmland under 0.9.0'sautoalike. A per-requesttool_call_format: "auto"andreasoning_effort: "max"also returned 200 on all three. A loader that passes the raw string still gets a raise underxml, with a message that names theautoopt-in. - The template keeps two marked changes (the
tool_call_formatsetting,reasoning_effort); its comments say why the default isxml. - Ollama is unaffected. It runs the Go
template/ModelfileTEMPLATE, which keeps JSON tool calls, and never the GGUF's embedded template. - README: "Tool / function calling" now explains the parser constraint, the
argument decoding and the opt-in; the loader table, the tool-calling summary, the
file table and the large-arguments limitation follow.
scripts/check.sh,scripts/build.shandscripts/strip_mtp.pyno longer say the upstream template returns a 500 on the second tool-call turn; they name the raise it does have. - The bundled GGUF is restamped with the new template -
tokenizer.chat_templateonly, tensors identical: sha2561e3249a2dd6512dff463e44a07c7ad165d833f28a2015af98e35f2013ef0502f(wasfb3796d4f5081e47da331771c0a5d7467123a03950762465c1488cf1eeff85b6). An Ollama user re-pullinghf.co/FoolDev/Thanatos-27B-HERETICdownloads the ~17 GB blob again. - Verified on the restamped blob, CPU only. Tensors and every metadata key but
tokenizer.chat_templateare byte-identical to the 0.9.0 blob (all 851 tensors compared), andscripts/verify_arch.pypasses. On llama-server master97e4ca7with the embedded template and default flags, a real first turn returned one parsedget_weathercall, and the replay of that call with its stringargumentsplus a tool result returned HTTP 200 with the right answer (both withreasoning_effort: "none").scripts/live_check.shon Ollama 0.33.3 passed all 17 checks: Ollama still selects the Go template, a first-turn call comes back once, a replayed string-argument tool result is answered, and reasoning replay and the effort tiers are unchanged (232 / 187 and 375 / 330 prompt tokens, as in 0.9.0).
Changed (checks follow the default)
scripts/check_chat_template.pysections 1-3 now assert that the default renders the XML form and the XML instructions, byte-identical totool_call_format='xml', and still refuses unconverted string arguments; thatautorenders string arguments as JSON with matching instructions; and that every format value renders mapping arguments: 48 assertions, up from 45.
[0.9.0] - 2026-09-13 — 5ce0b54
Changed (earlier turns' reasoning is replayed again)
- Reasoning from earlier assistant turns is back in the prompt, on both
runtimes. 0.7.0 stopped replaying reasoning from turns before the latest user
message; this release reverses that. A multi-turn conversation now costs more
prompt tokens: every earlier trace the client sends back stays in the prompt, and
prefill grows with each one - the README's measurement is 181 prompt tokens
without and 464 with, on a 3-message conversation whose first assistant turn
carried a ~240-word trace. On a CPU-only host prefill is the slow part.
- Ollama - the thinking block in the
ModelfileTEMPLATE and the bridgetemplatenow opens with{{ if (and $.IsThinkSet .Thinking) -}}instead of Qwen's stock{{ if (and $.IsThinkSet (and .Thinking (or $last (gt $i $lastUserIdx)))) -}}, so every assistant message that carries reasoning renders<think>…</think>. The$lastUserIdxloop stays, for anyone who edits the condition back. Ollama never passes reasoning to the GGUF's embedded template, so on Ollama only the Go template matters. - llama.cpp -
chat_template.jinjarestores heretic-ara's line verbatim:{%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %}. Earlier turns' reasoning is replayed by default, as upstream does, including upstream's empty<think>\n\n</think>block for an earlier assistant turn without reasoning; the template reads onlypreserve_thinking, which llama-server derives frompreserve_reasoningand--no-reasoning-preserve. The template now has two marked changes (tool_call_format,reasoning_effort).
- Ollama - the thinking block in the
- The client has to send the reasoning back. Ollama
/api/chatreads each assistant message'sthinkingfield; Ollama/v1/chat/completionsreads onlyreasoning(it silently dropsreasoning_contentandthinking, and an assistant message whose content is an array of parts keeps its reasoning only if it also hastool_calls); llama-server's/v1reads onlyreasoning_content. From the servers' source (Ollama v0.33.3 and v0.34.0); the/api/chatand/v1fields were then checked live (below). - Turning it off: on llama-server,
--no-reasoning-preserve, orpreserve_thinking: falseorpreserve_reasoning: falsein a request'schat_template_kwargs; on Ollama, change the condition back to Qwen's stock one (do not delete the block). A live tool-call chain keeps its reasoning either way. The README section Thinking is not replayed across turns is now Thinking is replayed across turns, with the fields each endpoint reads and both ways to turn it off; theModelfilecomment, the file table and the list of template changes follow. - Tested on llama.cpp with a 260K-parameter test model and
--chat-template-file(the template, not the model, decides the prompt), via/apply-templateand/v1/chat/completions, on master4a89937(b10941), b10760 (the llama-server Ollama 0.33.3 bundles) and b10519 (LM Studio's avx2 CPU runtime 2.30.0): with default flags earlier reasoning was replayed; with--no-reasoning-preserve, or per requestpreserve_thinking: falseorpreserve_reasoning: false, it was not;preserve_thinking: truereplayed it even against--no-reasoning-preserve; a live tool-call chain kept its reasoning in every mode; no request returned an error. Master (b10763 and later) passespreserve_reasoning=trueunless started with--no-reasoning-preserveand logs "chat template supports preserving reasoning, it is enabled by default"; b10760 and b10519 pass nothing, log "consider enabling it via --reasoning-preserve", and still replay, because the template's own default is on. - The bundled GGUF is restamped with the new template -
tokenizer.chat_templateonly, tensors identical: sha256fb3796d4f5081e47da331771c0a5d7467123a03950762465c1488cf1eeff85b6(wasc9895b2acc3defb3fd2def312e36915c11e2565f101584e12bc6c6a537262415). An Ollama user re-pullinghf.co/FoolDev/Thanatos-27B-HERETICdownloads the ~17 GB blob again. - Verified on the restamped blob, CPU only.
scripts/live_check.shon Ollama 0.33.3 passed all 17 checks: Ollama still selects the Go template, an earlier turn's reasoning is replayed (232 prompt tokens with it, 187 without) and a live tool chain keeps its own (375 / 330). Through Ollama's/v1(measured with the Janus sibling's blob; the Go template is byte-identical), an earlier turn carryingreasoningcounted 215 prompt tokens and one carryingreasoning_content187, the same as none. llama-server master4a89937with the embedded template and default flags rendered the 3-message conversation in 111 tokens with the earlier turn'sreasoning_content, 85 with no reasoning (the empty think block) and 85 with areasoningfield instead (dropped);--no-reasoning-preserveor eitherfalsekwarg gave 81, andpreserve_thinking: trueagainst the flag 111. A real turn after replayed reasoning and a real tool-result turn with string arguments both returned HTTP 200 with the right answer.
Fixed (template comments deferred from 0.8.0)
- The comment fixes listed under 0.8.0's "Known (deferred to the next GGUF
rebuild)" are done, riding on this release's restamp, so that entry is
resolved: the
tool_call_formatblock inchat_template.jinjais now markedCHANGED FROM UPSTREAM, as the header says every change is, and the old no-replay comment, with its claim that llama-server enablespreserve_thinkingby default, is gone along with the change it described.
Changed (checks expect replay)
scripts/check_chat_template.pysection 7 now asserts that earlier reasoning is replayed by default and withpreserve_thinking=True, dropped withpreserve_thinking=False, that a conflictingpreserve_reasoning=Truewithpreserve_thinking=Falsedoes not raise, and that a live tool-call chain keeps its reasoning withpreserve_thinking=False: 45 assertions, up from 44.scripts/check_go_template.py(check 9) now requires(and $.IsThinkSet .Thinking); Qwen's stock condition fails it.scripts/live_check.shcheck 4 now expects an earlier turn's reasoning to be replayed: more prompt tokens with it than without. Still 17 checks.
Documentation (upstream fix and report linked)
- The Ollama fix is proposed upstream as
ollama/ollama#18391: it adds the two
missing
String()methods, so{{ .Function }}renders JSON again once merged and released. This repo's{{ json .Function }}renders the same either way. - The Vulkan iGPU memory trap is reported upstream on
ggml-org/llama.cpp#27360,
with the mechanism and the
--no-hostmeasurements from the README's GPU notes. Both READMEs link the two.
[0.8.1] - 2026-09-11 — 43af6bc
Documentation (upstream report)
- The Ollama bug behind 0.8.0's tool-definition fix is reported upstream as
ollama/ollama#14601, with this repo's
findings posted there (comment):
the cause (commit e51dead6, first in v0.14.0), a model-free repro on current main, the
one-file fix, and that open PR #18318 does not reach it. Ollama's own library Qwen models
hit it too. If Ollama adds the missing
String()methods,{{ json .Function }}keeps rendering the same JSON, so nothing here needs to change. The README's Ollama tool-calling section now says why the template uses it.
Documentation (GPU offload on the Strix Halo iGPU measured)
- The card now explains why
num_gpualone is not safe on a Vulkan integrated GPU, and what partial offload buys. On this iGPU llama.cpp (the build Ollama 0.33.3 bundles included) turns mmap off and places every non-offloaded layer in GPU-reachable host memory from the same pool; a llama-server run filled the pool and crashed the desktop.LLAMA_ARG_NO_HOST=1keeps those layers in RAM (verified: no Vulkan_Host model buffer). Measured on the Janus sibling (same architecture) with a private Ollama 0.33.3 +ollama-vulkan(flash attention, q8_0 KV,num_ctx4096): 4.8 / 4.9 / 5.3 / 5.7 tok/s atnum_gpu4 / 8 / 16 / 24, against about 5.0 on CPU; the 32-layer step ran the 27 GiB host short of RAM (GTT is system RAM) and was stopped.
Documentation (LM Studio tested; loader claims narrowed)
- LM Studio's CPU runtime passed the same 25 checks as standalone llama-server.
Its
llama-serverbinary (llama.cpp b10519, the engine LM Studio drives) was run directly with--device noneon both published blobs: one call per first-turn tool request (three prompts, with and withoutparallel_tool_calls), a single streamed index, the string-argument replay answered, everyreasoning_effortvalue accepted, the effort tiers rendered, earlier turns' reasoning not replayed and a live tool chain's kept. - The LM Studio app itself could not be kept off the GPU, so it stays untested. In two
guarded attempts (snapshot of every LM Studio file, CPU runtime selected, Vulkan
hidden,
--disable-gpu), LM Studio 0.4.24 loaded the model with a newer CPU runtime (avx2 2.34.0) than the one selected, and that runtime's process opened the GPU; the test's guard stopped it before any real GPU allocation, and every LM Studio file was restored byte for byte. LM Studio's own API layer (per its source it mapsreasoning_effortonly to thinking on or off) remains untested. - The card no longer promises tool calls in loaders nobody tested. It said LM
Studio, Jan, KoboldCpp and llama-cpp-python get working tool calls with no
configuration. Now it says llama-server and LM Studio's runtime do (both tested);
llama-cpp-python renders the tools but, per its source, does not parse the model's
<tool_call>output; Jan and KoboldCpp are untested. make helpnow describes whatmake checkruns (lint, bridge sync, the chat-template render checks and the Go-template guard).
[0.8.0] - 2026-09-11 — 3b0a3f8
Removed
scripts/heal_hf_pull.shandmake heal-hf. They made a qwen36-stamped pull load in place, but every such blob is the May 2026 build on vanillaQwen/Qwen3.6-27B, so a healed tag ran the wrong model; since 0.7.0 the docs have said to pull again instead.scripts/rename_arch.pyand the qwen36 branch ofmake load-bundlestay. Older entries below still mention heal-hf as history.
Fixed
- Tool definitions reached the model as Go struct syntax, not JSON, on every Ollama
since 0.14.0. The Go template printed each tool with
{{ .Function }}. Ollama 0.14.0 (#13525) made.Functiona struct type with noString()method, so Go's template engine printed{get_weather Get current weather for a city {object <nil> <nil> [city] {...}}}inside<tools>, where the model expects a JSON schema. Tool calls still worked in every test, because the tool and parameter names survive, but the model never saw a well-formed schema. The template now uses{{ json .Function }}, which renders{"name":"get_weather","description":..., "parameters":{...}}. Established by rendering with Ollama's own template package at v0.33.3; the struct is unchanged in 0.34.0 and on main, and thejsonfunction dates from 0.2.6, so the fix also holds on older builds. check 9 now fails on a bare{{ .Function }}. Only thetemplatefile changes, not the GGUF, so a re-pull downloads a few kilobytes. Re-verified live on Ollama 0.33.3 withscripts/live_check.sh: 17 of 17 on both models, one tool call each; with a well-formed schema, a live tool chain's prompt is 6 tokens longer (375 prompt tokens with its reasoning, 369 before). - The provenance note misattributed the
Teacher:field and thedatasets:list to the upstream base. Neither base's card lists datasets or names a teacher (Hub APIcardData.datasetsis empty for both, and neither base README mentions a teacher, distillation or Crownelius). The note now says they are carried over from earlier revisions of this card, unverified, and thatCrownelius/Opus-4.7-Reasoningis not publicly reachable. The 0.7.0 entry's explanation of that 401 ("gated") is corrected: the Hub returns the same 401 for a private or missing repo, while a gated repo answers 200.
Documentation
- Verified on Ollama 0.34.0 (CachyOS
0.34.0-1.1, already on some CachyOS mirrors though not yet the official ones) withscripts/live_check.shin an isolated daemon on CPU: 17 of 17 checks on both models, with the same numbers as on 0.33.3 - the Go template selected with thinking detected, one tool call athigh, prior-turn reasoning dropped (187 = 187 prompt tokens), a live tool chain's kept (369 > 324), and the effort lines (159 unset and medium, 185 low, 197 high and max). Its bundled llama-server is still b10760. The README's and Modelfile's "verified on 0.33.3" notes now name both versions.
Known (deferred to the next GGUF rebuild)
- Three comments inside
chat_template.jinjaare inaccurate: the header says each of the three changes is markedCHANGED FROM UPSTREAM, but thetool_call_formatdefault isn't; and the no-replay comment says llama-server enablespreserve_thinkingby default, which only recent builds do. The template is stamped into the GGUF, so a comment fix means a new blob and a full ~17 GB re-download for every Ollama user. They will go in with the next functional template change.
[0.7.0] - 2026-09-11 — 0326c41
Changed (reasoning effort now reaches the prompt on Ollama)
- The Go template adds the reasoning-effort line. Ollama's default path runs the
Go template, which until now added no effort instruction for any value (only
nonechanged the prompt, by closing the think block). It now reads Ollama's think level ($.ThinkLevel) and adds the linechat_template.jinjaadds:high,xhigh,maxandultra(which Ollama passes on ashighormax) get the xhigh line;lowandminimal(passed aslow) the low line;mediumand an unset value (both passed asmedium) none. Unlikechat_template.jinja, whose default is xhigh, an unset value adds no line: Ollama sends it asmedium, the same as an explicitmedium. The mapping is from Ollama's v0.33.3 source (thinkFromReasoningEffort,ThinkValue). An outerif $.ThinkLevelkeeps the template rendering on Ollama builds that predate the field, and the block sits before the system prompt, outsiderange .Messages, so it leaves the.Thinkingdetection that check 9 guards alone. - Verified live on Ollama 0.33.3 (
scripts/live_check.sh: isolated daemon, CPU) against the restamped blob: Ollama still selects the Go template with thinking detected; on a one-line chathighandmaxeach added the xhigh line (197 prompt tokens against 159),lowthe low line (185), andmediumand an unset value nothing (159 and 159); a first-turn tool call athighcame back once; all eight values returned 200, andnoneswitched thinking off.
Fixed (llama-server replayed earlier reasoning anyway)
- Recent llama-server builds switch reasoning replay on by default for any chat
template that honours
preserve_thinking: they probe the template at load time and, unless started with--no-reasoning-preserve, setpreserve_thinking=trueon every request (llama.cppcommon/arg.cpp:958-960,common/jinja/caps.cpp). b10760, the build Ollama 0.33.3 bundles, does not; it only logs a suggestion to enable it. This template honoured the variable as an opt-in, so the "not replayed across turns" default never applied on a current stock llama-server. Tested on llama.cpp mastera2878d3(CPU) against the published blob: an earlier turn's reasoning was in the rendered prompt by default, and absent with--no-reasoning-preserve. - The template now ignores
preserve_thinking. Earlier turns' reasoning is never replayed; the turn in progress, a tool-call chain included, keeps its own.--reasoning-preservehas no effect on this template any more; the README gives the one-line edit that restores replay.check_chat_template.pynow asserts the variable is ignored. - Re-tested on llama.cpp master
a2878d3with the restamped blob and default flags: the earlier turn's reasoning is no longer in the rendered prompt, a live tool chain's still is, and the llama-server warning about preserving reasoning is gone. 25 of 25 checks pass. - The bundled GGUF is restamped with the new template -
tokenizer.chat_templateonly, tensors unchanged: sha256c9895b2acc3defb3fd2def312e36915c11e2565f101584e12bc6c6a537262415(was357b0d0af80b60b810a7c6cbbb8044a406ad1f740de39e6167feb4233e4df149).
Documentation (standalone llama.cpp tested)
- Standalone llama-server, llama.cpp master
a2878d3on CPU, the published blobs of both models: 25 of 25 checks each. Six first-turn tool calls at temperature 0 (three prompts, with and withoutparallel_tool_calls) each came back exactly once, with cleancontentandfinish_reason: tool_calls; a streamed call used a single index; a replayed tool result with JSON-string arguments was answered; all eightreasoning_effortvalues Ollama accepts, plus"HIGH"and an unknown word, returned 200,nonewith no reasoning; and the rendered prompt carried the tiers in the README table. The README's "untested on llama.cpp" notes are replaced with these results, and the duplicate-call limitation now says it did not reproduce there. Nor did it under b10760, the llama.cpp build Ollama 0.33.3 bundles and the one the duplicate came through: 25 of 25 on the published Janus blob, six first-turn calls and a streamed run each returning one call. - Throughput measured on the current blob, CPU only: 5.01 tok/s at Q4_K_M
(
make bench, Ollama 0.33.3 CPU backend on a Ryzen AI Max+ 395, flash attention, q8_0 KV). The CPU-only row said1-3 tok/s. The Vulkan figures (12.3 at Q3_K_S, ~9.3 at Q4_K_M) are relabelled: the README said they were taken on the heretic-v2 blob and carry over, but the Modelfile dates them to 2026-05-19, on the Qwen 3.6 27B builds before both base swaps, and they have not been re-measured.
Added (checks for what render tests miss)
check.shcheck 9,scripts/check_go_template.py. It fails if the Gotemplateloses the.Thinkingreference insiderange .Messagesthat Ollama infers thinking support from, or if Qwen's stock$lastUserIdxcondition is narrowed - the two regressions below, each of which passed every existing check. It also asserts the$lastUserIdxloop and the tool round trip Ollama credits (.Tools,.ToolCalls,eq .Role "tool",<tool_response>). Negative-tested: it fails on the deleted block and on the$last-only condition.scripts/live_check.sh(make live-check). It starts an isolated Ollama (its own port and model store), imports the GGUF with this repo'sModelfile, and checks what only a live server shows: which template Ollama selects, that a first-turn tool call comes back exactly once, that a replayed tool result with JSON-string arguments gets answered, that a prior turn's reasoning is dropped while a live tool chain's is kept (prompt tokens on a fresh load), and everyreasoning_effortvalue Ollama accepts. It needs the real GGUF, RAM to load it and several minutes, so it is not part ofcheck.sh. Run against the restamped blob before this release: 17 of 17 checks passed.
Fixed (heal-hf is no longer the advice for old pulls)
- The README told anyone with a qwen36-stamped pull to run
make heal-hf. That makes the blob load, but every qwen36-stamped blob dates from 2026-05-19/20, before both base swaps (the bundled blob moved to heretic-v2 on 2026-07-24 and to heretic-ara on 2026-08-25; a docs-only heretic-v2 swap on 2026-05-23 was reverted). A healed tag still runs the May build on vanillaQwen/Qwen3.6-27B, with the template and params it was pulled with; the heal rewrites only the model layer. The README,examples/README.md, the Makefile help andheal_hf_pull.sh(header and closing message) now say to pullhf.co/FoolDev/Thanatos-27B-HERETICagain instead. The script stays for anyone who wants the old build to load.
Fixed (Ollama runs the Go template again)
- Disabling preserve-thinking had silently switched Ollama to a different
template. Deleting the
{{ if (and $.IsThinkSet .Thinking) }}block from the Gotemplatealso deleted the only thing Ollama uses to infer that template's "thinking" capability: a.Thinkingfield insiderange .Messages, wrapped in<think>/</think>. Ollama 0.33.3 (what this was verified on) compares that capability list with the GGUF's embedded Jinja template at load time and prefers the Jinja template when it advertises more, keeping the Go template on a tie; with thinking gone, the Jinja template won. Established from Ollama's source at v0.33.3 (shouldPreferChatTemplate,thinking.InferTags) and from the service log on the sibling Janus (same Go template; this model's switch was not logged), where one unchanged 0.33.3 process loggedselected=go_template go_template="[completion tools thinking]"before the change andselected=gguf_chat_template go_template="[completion tools]"after it. - That switch is what exposed the
reasoning_effort500s fixed below. It also exposed a duplicate-call problem: on the embedded-template path a single tool call came back twice (seen once, on Janus). A separate raw generation showed the model drafting the call inside<think>before emitting it once after</think>, so the parser most likely matched the draft - inferred, not confirmed; not tested at the time on Thanatos, standalone llama.cpp or LM Studio (on standalone llama.cpp it later did not reproduce; see "Documentation (standalone llama.cpp tested)"). And, per Ollama's v0.33.3 source (not tested live), a setup forcing the Go template (OLLAMA_GO_TEMPLATE=1) would have lost thinking support and rejected thinking requests with HTTP 400. - The fix restores the thinking block with Qwen's stock condition -
{{ if (and $.IsThinkSet (and .Thinking (or $last (gt $i $lastUserIdx)))) -}}, with its$lastUserIdxloop - byte-for-byte the Go template this repo shipped before its preserve-thinking change. Reasoning from assistant turns before the latest user message is still dropped; the turn in progress, a tool-call chain included, keeps its own; Ollama detects thinking support again, the capability lists tie, and the Go template wins. No blob change: the Go template is its own file. - A first attempt restricted the block to a trailing assistant message (
$lastonly). It was published briefly and was stricter than Qwen stock: on Ollama it also dropped a live tool-call chain's reasoning once the tool result followed. Caught in review and replaced by the stock condition above. - Verified in an isolated Ollama 0.33.3 daemon against the published blob, with
the stock condition:
selected=go_template go_template="[completion tools thinking]"; a first-turn tool call atreasoning_effort: "high"- the value that used to return 500 here - returns exactly one call; reasoning from a turn before the latest user message is dropped - 187 prompt tokens with a priorthinkingfield and 187 without - while a live tool-call chain keeps it - 369 with, 324 without - each request on a fresh load; andreasoning_effort: "none"switches thinking off on this path. - The
Modelfilenow explains why the block is load-bearing, and the README gains a Which template Ollama runs section, a Known limitations entry for the duplicate-call behaviour, and a correction to Reasoning effort: on Ollama's default path the Go template runs, and it did not read the tier at the time (it does now; see "Changed (reasoning effort now reaches the prompt on Ollama)").
Fixed (an unfamiliar reasoning_effort no longer fails the request)
reasoning_effort: "high"returned HTTP 500 - so did most other values. Ollama 0.33.3 was rendering the Jinja template embedded in this GGUF for this model (its log reportedselected=gguf_chat_template- a side effect since reverted, see above), so the template's own validation ran on every/v1/chat/completionscall. Upstream accepted onlyxhigh,mediumandlow, and raised on everything else:high- the value most OpenAI-compatible clients send - plusminimal,none,max,ultra, and an explicit JSONnull, whichreasoning_effort|default('xhigh')does not catch becausedefault()replaces only an undefined value. A runtime that passes the raw value (llama-server) hit every one of those; Ollama foldsxhighandultraintomaxandminimalintolowand sends no value fornoneornull, so there the failures werehigh,xhigh,maxandultra.- The template now normalises instead of validating. Values are lower-cased and
trimmed;
low/minimalselect low;mediumselects medium;none(orfalse) switches thinking off for that render, including the closed<think>prefill; everything else -high,xhigh,max,ultra, empty,null, anything unrecognised - uses upstream's xhigh default. Nothing raises. - Verified on the real engine, not just in a Python renderer. An isolated
Ollama 0.33.3 daemon running the re-stamped blob selected
gguf_chat_template- the path a normal install took until the Go template's thinking block was restored (see above) - and returned HTTP 200 forhigh,max,none,null,minimalandmedium. - Mixed case is Ollama's call, not the template's. Ollama's
/v1layer rejects anything outside its eight lower-case values with HTTP 400 (invalid reasoning value: "HIGH") before the template runs. The template itself lower-cases, so a runtime that forwards the raw value gets it accepted (llama.cpp does, as later tested; LM Studio is untested). scripts/check_chat_template.pypins it. Every value a client might send - the eight Ollama forwards, explicitnull, empty string, garbage, numbers, booleans - must render;high/xhigh/max/ultramust select the top tier,low/minimalthe low tier,mediumthe medium tier, andnonemust switch thinking off.- Bundled GGUF re-stamped; only
tokenizer.chat_templatechanged, all 851 tensors carried through untouched.
Changed (reasoning is no longer replayed across turns)
Partly superseded later in this release: the template now ignores
preserve_thinking(it never readpreserve_reasoning), so the opt-in below no longer works; see "Fixed (llama-server replayed earlier reasoning anyway)". The README gives the template edit that restores replay.
- Traces from earlier assistant turns are dropped from the prompt. Only the
current turn generates a
<think>block. This repo previously preserved them deliberately — a documented divergence from Qwen stock — so the model could see how it reached its previous answers. The cost was context: every retained trace stayed in the prompt and prefill grew with it. Measured A/B on this model, same 3-message request, only the template differing: 192 prompt tokens without replay vs 221 with.Retracted - see "Ollama runs the Go template again" above. Removing the block had also switched Ollama to a different template, so that comparison did not isolate the thinking block. Measured again with one template and a fresh load per request: 187 prompt tokens with a priorthinkingfield and 187 without, on both models; a live tool-call chain keeps its trace (369 with, 324 without).- Changed on both runtimes, so they stay consistent:
- Ollama — the
{{ if (and $.IsThinkSet .Thinking) }}block is gone from theModelfileTEMPLATE and the bridgetemplate(both, socheck_bridge_sync.pystays green). Byte-equivalent to before for any turn that carried no trace. Deleting it outright was a mistake - it is back, with Qwen's stock condition; see "Ollama runs the Go template again" above. - llama.cpp —
chat_template.jinjanow defaultspreserve_thinkingto false where upstream defaults it true. Both bundled GGUFs were re-stamped so the embedded template matches; onlytokenizer.chat_templatechanged, all 851 tensors carried through untouched.
- Ollama — the
- Reasoning is still returned to you — Ollama's
thinkingfield, llama.cpp'sreasoning_content. It is simply not fed back in on the next turn. - Opt back in with
preserve_thinking=true(orpreserve_reasoning=true) as a template kwarg on llama.cpp; on Ollama it is a template edit - change the thinking block's condition to(and $.IsThinkSet .Thinking), as the README shows. - Reasoning emitted after the last real user query is still kept, on both runtimes, so a live tool-call chain does not lose its trace mid-flight.
scripts/check_chat_template.pygrows to 19 assertions; the four new ones pin exactly this: dropped by default, replayed on explicit opt-in, dropped on explicit opt-out, and the live tool chain keeping its own trace.
Fixed (leftovers caught by a post-change verification sweep)
The "Embedded-jinja path" section still described the bundle as carrying upstream's XML-defaulting template. True before the blob was stamped, false after: the bundled GGUF embeds this repo's
chat_template.jinja, so that path works with no flag. The section now attributes the XML default to a quant pulled straight from upstream, which is where it actually applies.The shipped template and the blob it is stamped into had drifted. Two problems in
chat_template.jinjaitself: its invalid-value error still namedxmlas the default after this repo changed the default toauto, and the header advertised--chat-template-kwargsas an equivalent alternative. Both fixed, and both bundled GGUFs re-stamped so the embedded template is byte-identical to the repo file again (tensor data untouched — all 851 tensors carried through unchanged, only thetokenizer.chat_templateKV differs).scripts/verify_arch.pynow asserts that too. Nothing caught the drift: the template is data, the blob is an LFS pointer in a fresh clone, and a stale embedded copy silently reinstates the tool-calling failure for every loader that reads the GGUF rather than an external file.verify_arch.pyalready loads the real weights, so it now comparestokenizer.chat_templateagainstchat_template.jinjaand prints the re-stamp command on mismatch. Verified in both directions against a correctly-stamped blob and a stale one.--chat-template-kwargsis not an alternative in this repo, and the docs said it was.tool_call_formatis added bychat_template.jinja; the stock upstream template here has no such setting, so passing the kwarg against it does nothing. That shortcut works only for the Janus sibling, whose base template already carried the switch. Corrected in the template header and the CHANGELOG.The claim that pinning
xmlrefuses string arguments "with upstream's message" was also wrong — upstream had no such check and simply died with a rawitemsTypeError; this template raises an explicit message instead.scripts/cap_ctx.sh's LFS guard could never fire. A fresh clone leaves the bundled GGUF on disk as a ~136-byte git-LFS pointer, and[[ -f ]]is true for that pointer — so the check passed andollama createwas handed a text file. It now checks the GGUF magic bytes, and the remedy it prints is one that works (git lfs pull, orload_bundle.sh's.cache/path) rather than an operationload_bundle.shnever performs.The card claimed
load_bundle.sh"smudges the LFS pointer". It does not — it downloads into.cache/and re-points its own variable, leaving the working-tree pointer untouched. Corrected in the README (and the Makefile help where it said the same).Leftovers from the superseded tool-call diagnosis removed.
strip_mtp.py's docstring still described llama.cpp's "--jinja grammar" rejecting the XML dialect and returning an empty completion; that account was retracted. It now describes the real failure (the template raises on JSON-stringarguments; llama-server returns a 500 on the second turn).ollama/ollama#14575follow-through. The Vision section still concluded "until the Ollama upstream issue is fixed"; the issue is closed, so the text now says the table has not been re-verified since rather than asserting it is still broken.Sibling-only fixes back-ported. The 32 GB "still OOMs" contradiction and the 24 GB "full Q4 offload" claim were corrected in Janus (and in this repo's README hardware table) but survived here in the
/v1callout and theModelfileworking-configurations block. Both now match the ~26 GB the card computes.scripts/fetch_vision.shquoted the retired ~931 MB projector size and printed a follow-up command loading the raw upstream quant; both corrected.examples/llama_cpp_quickstart.pyandexamples/llama_cpp_vision.pystill pointed--ggufatQwen3.8-27B-heretic-ara.Q4_K_M.gguf, the MTP-carrying upstream file the same files warn against. They now use the bundled blob. The vision example also quoted the retiredqwen35moearch stamp and a stale "until that lands".check_bridge_sync.py's commit citation was left reading "33458f7 / 70ccef1" by the earlier fix;70ccef1does not exist, so it now cites only the real commit.
Changed (the bundled blob carries the fixed chat template)
Thanatos-27B.Q4_K_M.ggufwas restamped. Shippingchat_template.jinjabeside the weights never reached LM Studio, Jan, KoboldCpp or llama-cpp-python: they read the template embedded in the GGUF and have nowhere to pass--chat-template-file, so on those loaders tool calling still died with the upstream template'sCan only get item pairs from a mapping— a llama-server 500 on the second turn of every tool call.- The source was this repo's own published blob (sha256
188dfa6bc3210b29c7cb08073b2431865305a40c79d3cbbcc5a7c1ef18f88710, verified before use), so what ships is exactly what shipped before with one metadata key changed. All 851 tensors are byte-for-byte identical — checked by hashing each tensor in both files — so there is no re-quantization and no quality change. The file grows by 4,928 bytes, the size difference between the two templates. New sha256:6ed2d5e05d74abfc0dc38135fc2169fee3df1f80e2f491717951d1200db168ba. - Verified before publishing: 14/14 Architecture claims via
scripts/verify_arch.py; loads in Ollama asqwen35/ 26.9B dense withtoolsandthinking; and a full tool-call round-trip on/v1/chat/completions, including the replay turn whose stringargumentsis the failure this fixes. --chat-template-fileis now only needed for a quant pulled straight from upstream. Docs updated accordingly.
Fixed (numbers and references the audit caught)
Sibling references updated for Janus's blob swap. The Janus-vs-Thanatos note said Janus's bundle "is still the old
21 GB MoE quant and the re-quantization is pending"; that landed, so both siblings now ship what their cards describe. The Q4_K_M size row is also exact rather than rounded to a shared "17 GB": Thanatos ~16.5 GB, Janus ~17.3 GB.scripts/check_bridge_sync.pydocstring corrected — it cited commit70ccef1, which exists in neither repo; the real one is33458f7.make helpdescribed the filename guard backwards. It said "block dot-pattern footgun"; the guard bans the dash form, because mradermacher names heretic-ara quants with a dot.make cleannow sweeps*.stripped.gguf, thebuild.sh/strip_mtp.pyintermediates that.gitignorealready knows about andcleanwas leaving on disk (~17 GB each).The Janus-vs-Thanatos table claimed the heretic-ara source weights have no MTP head. They do — read straight from the base GGUF's metadata:
nextn_predict_layers = 1,block_count = 65. Three other places in this same README already said so. Both siblings ship MTP-preserved sources and both bundles are stripped.Hardware table promised "full Q4 offload" on a 24 GB card, contradicting the same section's ~26 GB total at the shipped default. Now says partial offload, with the
num_ctxtrim needed for a full one.imatrixfrontmatter tag removed. The bundle comes from mradermacher's static GGUF repo, not thei1importance-matrix one.CITATION.cffun-stalled — it was pinned at0.1.0/2026-05-02while the CHANGELOG had reached0.6.0/2026-05-19and a full base rebase had landed since.ollama/ollama#14575is no longer described as open — it closed as completed on 2026-08-04 (checked against the GitHub API). The vision notes now say so and flag that the loader table has not been re-verified since.transformers>=4.51floor dropped from the examples: it predates Qwen 3.8 by a major version.
Added (chat template is stamped into the GGUF at build time)
scripts/strip_mtp.pygained--chat-template FILE, which replaces the GGUF'stokenizer.chat_templatein the same pass that drops the MTP block.scripts/build.shnow passeschat_template.jinjaautomatically when the file is present. This matters because--chat-template-fileis a llama.cpp flag: LM Studio, Jan, KoboldCpp, Open WebUI and llama-cpp-python all read the template embedded in the GGUF and have nowhere to put that flag, so they were still getting the upstream XML tool-call form, which raises on the JSON-stringargumentsan OpenAI-compatible client replays — a llama-server 500 on the second turn of every tool call. A GGUF built here now carries the working template, and those loaders need no flags at all.- The script stays a strip tool: with
--chat-templateon an already-MTP-clean input it rewrites the file purely to swap the template (the usual hardlink passthrough cannot carry one), and still hardlinks when neither a strip nor a stamp is needed. Tensor data is copied byte-for-byte in every path — no re-quantization, no quality change. An empty template file is refused rather than stamped. - Verified against synthetic GGUFs across all six paths: MTP-strip alone;
MTP-strip plus stamp; clean input plus stamp (rewrites, block_count preserved,
tensor bytes intact); clean input with no stamp (hardlink, same inode); empty
template file rejected; and this repo's real
chat_template.jinjastamped and read back byte-identical. - Documented in the README: a locally built GGUF needs no flag, and there is a one-liner to restamp a GGUF you already have.
Fixed (provenance, and a CLI-detection footgun)
base_model_relation: finetune→quantized. Nothing is finetuned in this repo; it repackages and quantizes an upstream abliteration. A new Provenance note under the header now says so plainly and reframes theTeacher:field and the frontmatterdatasets:list as the inherited lineage claimed by the upstream base — attribution, not a training record. Nothing here reproduces them and no evaluation in the card measures them.- Dataset owner casing corrected to
Crownelius/on the two entries that had it lowercase; the lowercase form 307-redirects on the Hub. (Verified against the Hub API:Crownelius/Crow-Heretic-TeichAI-Unifiedandmicrosoft/rStar-Coder,peteromallet/dataclaw-peteromallet,openbmb/UltraData-Mathresolve 200;Crownelius/Opus-4.7-Reasoningreturns 401, which the Hub also returns for a private or missing repo (a gated one answers 200), so it is not publicly verifiable.) scripts/build.sh,scripts/load_bundle.shandscripts/fetch_vision.shno longer trust the bare namehf. All three probedcommand -v hffirst, buthfis ambiguous: Arch'shfpackage ships an unrelated hidden-file utility under that name. On such a host every download path selected the wrong binary and failed with an argument error that says nothing about Hugging Face. A shareddetect_hf()now prefers the unambiguoushuggingface-cliand acceptshfonly when its--helpidentifies it as Hugging Face's. Confirmed on a machine with Arch'shfinstalled: the old probe selected/usr/bin/hf, the new one rejects it and reports that no HF CLI is present. The two-armedcasethat dispatched on the CLI name is gone — both spelldownloadidentically.scripts/check.shguard 6 is now multi-line aware. It was a line-anchored grep, so a command substitution split across backslash-continued lines was invisible to it — and one had been living inscripts/heal_hf_pull.sh:86the entire time the check reported green. Rewritten to join continuations into logical lines before matching, and verified against an injected decoy.scripts/heal_hf_pull.sh— the silent-exit footgun that385ed94fixed at line 67 was still present at line 86.MANIFEST="$(find ... 2>/dev/null | head -1)"spans three lines; underset -e+pipefaila failingfind(missing manifests directory) killed the script before the[[ -z "${MANIFEST}" ]]check could report anything actionable. Split using the same recipe as the earlier fix.
Fixed (vision path pointed at a GGUF that cannot load)
- Every documented llama.cpp and vision command loaded the raw upstream quant.
-m Qwen3.8-27B-heretic-ara.Q4_K_M.ggufis the mradermacher file, which — as this card's own Architecture section says — still carries the MTP / NextN layer that stock llama.cpp aborts on. The bundledThanatos-27B.Q4_K_M.ggufis that same quant already stripped, so the README's three vision recipes, the Vision stack listing,examples/llama_cpp_vision.pyandexamples/README.mdnow use the bundle. Besides fixing the crash this saves users a redundant ~17 GB download. A callout explains the MTP strip for anyone who does want an upstream quant. - mmproj size corrected
~931 MB→~928 MB. The f16 projector is 927,607,136 bytes; 931 MB is the BF16 projector in the Janus sibling's repo. - Smallest published quant corrected
Q3_K_S→Q2_Kin the README and theModelfile. mradermacher publishes Q2_K (10.9 GB) and IQ4_XS below the claimed Q3_K_S floor (12.3 GB);scripts/build.shalready listed them correctly.
Fixed (tool calling under llama.cpp / llama-server)
chat_template.jinja— the base model's own embedded template, patched only where the tool-call wire format is decided. It gains atool_call_formatsetting —auto(default),json,xml— mirroring what the Qwen 3.8 "safe" template does and what the Janus sibling pins.reasoning_effort, the vision handling and every other upstream behaviour are unchanged.The failure is on the second turn, not the first. Every OpenAI-compatible client hands assistant tool calls back with
function.argumentsas a JSON string. The XML tool-call form the base template defaults to needs the individual key/value pairs, and a chat template hastojsonbut no inverse, so it cannot decode one. The base template applies theitemsfilter to the string and dies. Reproduced by rendering the stock template from the base model's own GGUF metadata:Can only get item pairs from a mapping.Under
llama-serverthat raise is a 500: the model emits the tool call, the result is fed back, and the next request dies — an agent loop halts after exactly one call.autois lossless. It renders the prompt in JSON form only when a historical tool call carries string arguments, and is byte-identical to upstream's XML when every argument is a mapping — verified by rendering both templates over the same mapping-argument conversation and comparing output.tool_call_format='xml'still refuses string arguments — with an explicit message rather than upstream's rawitemsTypeError — and an unknown value is still rejected.The kwarg is NOT an alternative in this repo:
tool_call_formatis added by this file, and the stock upstream template has no such setting, so passing--chat-template-kwargsagainst it does nothing. (That shortcut works only for the Janus sibling, whose base template already had the switch.) The file exists for loaders with nowhere to pass kwargs (LM Studio, Jan, KoboldCpp), and is whatscripts/build.shstamps into the GGUF it builds.The Ollama path is unchanged.
Modelfile,template,systemandparamsare untouched — tool calling there already worked, and/api/chatand/v1/chat/completionswere re-verified end to end (single call, streaming, and the tool-result round-trip) before this change.scripts/check_chat_template.py, wired intoscripts/check.shas check 8 (somake checkand the pre-commit hook cover it), renders the template and asserts 15 properties — that string arguments do not raise, that they render as JSON, that mapping arguments still render, that eachtool_call_formatvalue behaves, that both<think>prefill directions are right, and that vision pads survive. The template is data, not code, sopy_compileandpyflakesnever look at it and a one-word edit restores the breakage silently.
Correction to the first two attempts at this fix
The initial commits shipped a hand-written template and a wrong diagnosis — that llama.cpp selected a Hermes-2-Pro grammar which disagreed with the template's XML instructions and terminated the decode, yielding an empty completion. That is not what happens. The real failure is the raise above, on the replay turn, surfaced as a 500.
Both earlier versions were also derived from the wrong source: the template read
out of this repo's pre-rebase GGUF, which is a different Qwen generation from
the current base. Reading the real embedded template out of each base showed the
two siblings do not even share one — Janus's is qwen3.8-safe-v3.1 (28,277
chars, with the tool_call_format switch already built in), Thanatos's is the
stock Qwen template (8,953 chars, XML only, but carrying reasoning_effort).
Shipping one identical hand-written file to both discarded the safe-v3.1 template
entirely on one side and reasoning_effort on the other. Each repo now carries a
patch of its own base template.
Changed (num_ctx default → 65,536)
- Baked
num_ctxdefault lowered262,144 → 65,536inparamsand theModelfile(bridge-synced). 262,144 remains the model's native window and theContext:header field is unchanged — what moves is only the default the model loads at. Rationale: the 262,144 default allocated a ~16 GB KV cache for ~38 GB total, which does not fit a 32 GB host, so the documented first step for most users was to override it back down. 65,536 is ~4 GB KV / ~26 GB total and loads without hand-tuning, while raising it toward the native window stays a one-line change for hosts with the RAM. - The TL;DR heads-up,
/v1callout, Hardware section, hardware table rows,Modelfilefootprint math andscripts/cap_ctx.sh's header were all reframed so 65,536 reads as the default, 262,144 as the native window, and 1,010,000 as the extensible YaRN ceiling.scripts/verify_arch.pyis untouched: it asserts the GGUF'scontext_lengthmetadata (262,144), which is a property of the weights, not of the baked default.
Fixed (documentation — /v1 silently defeats the sampling defaults)
- The card promised "loop insurance" that the OpenAI-compatible endpoint does
not deliver.
Recommended samplingdescribes the shipped profile as "temperature 1.0, notop_k, withtop_p 0.95+repeat_penalty 1.05kept as loop insurance". Ollama's/v1/chat/completionslayer applies OpenAI's own default oftop_p 1.0, overwriting the0.95baked intoparamsand theModelfile. Measured on this model, same request, same tag:top_p = 0.950on/api/chat,top_p = 1.000on/v1/chat/completions. - Combined with
top_k 0(disabled by design), a/v1request therefore samples with no truncation at all attemperature 1.0— every one of the 248,320 vocabulary entries live at its raw probability. The sampling values are unchanged (the warm,top_k-free profile is a deliberate Fable match); what is added is a callout saying the guard exists only on the native API, and thatmin_pandtop_kpass through/v1untouched whiletop_pdoes not.
Changed (thinking is now preserved across turns)
Superseded later in this release: earlier turns' reasoning is no longer replayed; see "Changed (reasoning is no longer replayed across turns)" and "Fixed (llama-server replayed earlier reasoning anyway)".
- The chat template renders the
<think>block of every assistant message that carries one, not just the current turn. The guard(or $last (gt $i $lastUserIdx))— Qwen's stock behaviour, which drops reasoning from every earlier turn — is removed, leaving(and $.IsThinkSet .Thinking). The four-line$lastUserIdxpreamble existed only to feed that guard and is deleted with it. Applied to bothtemplateand theModelfileTEMPLATEblock, so the HF Ollama bridge and localollama createbuilds stay in sync (check_bridge_syncgreen). - Measured effect. Two tags built off the same weights differing only in template, given a 3-message conversation whose first assistant turn carried a ~240-word thinking block: 181 prompt tokens with the old template, 464 with the new — the retained trace, rendered back into the prompt.
- The cost is context, and it compounds. Every retained trace stays in the
prompt, so reasoning-heavy multi-turn sessions grow much faster than upstream
and prefill time grows with them. Documented in the card
(
Thinking is preserved across turns) and in aModelfilecomment marking the divergence from Qwen stock, so it is not "fixed" back by mistake.
Changed (sibling comparison retired — Janus is no longer a MoE)
- The
Why a 27B variant?section is gone, replaced byJanus vs Thanatos.FoolDev/Janus-35B-HERETICrebased off the Qwen 3.6 35B-A3B MoE onto the same dense Qwen 3.8 27B family this model uses, so every dense-vs-MoE argument the card made about its sibling became false: "instead of the 35B-A3B MoE. Smaller, easier to deploy", "the lighter sibling to Janus-35B", "More capacity, more memory pressure", and a 12-row comparison table whose Architecture / Total params / Active params / Layers / Hidden size / GGUF size / Min host memory rows all described a model that no longer exists. The replacement compares what actually differs — ARA vs MPOA-style abliteration, immediate base, and whether the source weights carry an MTP head — and states plainly that everything else is shared. - Dropped a dangling benchmark. The old section cited "
27 tok/s for the MoE 35B at ~Q4" as a contrast to this model's ~12 tok/s; that figure was measured on a build the sibling no longer ships, so it is deleted rather than carried over. This repo's own measured figures (12.3 Q3_K_S, ~9.3 Q4_K_M) are untouched. - Sibling badge, intro paragraph,
llama.cpparch note, Hardware lead-in, Related models row, vocab bullet,Modelfiletemplate comment andCITATION.cffreference all reworded off "35B" / "MoE". TheJanus-35B-HERETICslug is deliberately unchanged everywhere — renaming it would break every published link — so the card now notes it as a pre-rebase artifact where it appears. - Known limitations no longer claims "Slower per token than the 35B-A3B sibling … if you optimize for tokens-per-second, the MoE wins." It now states the dense cost honestly and notes the sibling offers no speedup.
Fixed
scripts/cap_ctx.shno longer cites the retired baked default. Its header explained the helper by saying OpenAI/v1loads atnum_ctx 1010000and OOMs on <=48 GB hosts — stale the moment the baked default dropped to 262,144 in the same release. The rationale still holds at the new default (38 GB total), so the header now says that instead of a number the repo no longer ships. Matches the same fix in the siblingFoolDev/Janus-35B-HERETIC.
Changed (num_ctx default → the 262,144 native window)
- Baked
num_ctxdefault lowered1,010,000 → 262,144inparamsand theModelfile(bridge-synced). 262,144 is the model's native trained window — safe, no RoPE degradation — while the card still advertises extensible to 1,010,000 via the opt-in YaRN recipe (and theContext:header field is unchanged). At the new default the footprint is ~38 GB total vs ~84 GB at the 1.01M ceiling; the TL;DR, Hardware section,/v1callout, and Modelfile footprint math were reframed so 262,144 reads as the default and 1,010,000 as the extensible ceiling.
Changed (base rebased → heretic-org Qwen 3.8 27B heretic-ara)
- Rebased the card from the Qwen 3.6 27B Heretic v2 base to the Qwen 3.8 27B
heretic-arabase. The immediate base moves fromllmfan46/Qwen3.6-27B-uncensored-heretic-v2toheretic-org/Qwen3.8-27B-heretic-ara— a genuine Heretic-tool abliteration ofQwen/Qwen3.8-27Bvia the Arbitrary-Rank Ablation (ARA) method (0/100 refusals, KL ≈ 0.05). The base author necessarily changed:llmfan46has published no 3.8 release. GGUF quants now come frommradermacher/Qwen3.8-27B-heretic-ara-GGUF, the MTP-preserved variant fromcygnal/Qwen3.8-27B-heretic-ara-Q4_K_M-MTP-GGUF, and the vanilla reference projector/base fromunsloth/Qwen3.8-27B-GGUF. - The architecture is identical across 3.6 → 3.8 (both
model_type: qwen3_5): 64 layers, hidden 5120 / FFN 17408, 24 Q / 4 KV heads at head_dim 256, Gated-DeltaNet 48 V / 16 QK at head_dim 128, partial RoPE 0.25, vocab 248,320, 262,144 native context. So this is a relabel, not an architecture change — thegeneral.architecture: 'qwen35'stamp,scripts/verify_arch.pyexpected values, and thetemplate/system/paramsbridge files are all unchanged;verify_archandcheck_bridge_syncstay green. - Filename convention flipped dash → dot. heretic-ara GGUFs are named
Qwen3.8-27B-heretic-ara.${QUANT}.gguf(dot separator) where the old llmfan46 quants used dashes.scripts/build.sh(GGUF_NAME),scripts/fetch_vision.sh(mmprojmmproj-f16/mmproj-Q8_0, default precisionf16), theMakefile(PRECISION,cleanglob) and thescripts/check.shfootgun-grep were retargeted to the dot convention. Q3_K_S (~12 GB) is now published directly, so the old "no Q3_K_S in the repo" caveat is dropped. - Docs + metadata retargeted throughout: frontmatter
base_modeland theqwen36 → qwen38tag, the Base badge (Heretic_v2 → Heretic_ARA), the README (subtitle, TL;DR, What's-here, Architecture label, Vision, MTP → cygnal, arch-stamp family note extended to3.5 / 3.6 / 3.8, related models, credits),CITATION.cff(title / abstract / references / keyword), and theModelfileheader + source list. The Janus-35B-A3B cross-references stay on 3.6 (the sibling was not rebased), and the historical qwen35/qwen36 arch-stamp saga is preserved as-is. - Bundled blob swapped to the MTP-stripped 3.8 Q4_K_M.
The current heretic-ara GGUFs (
mradermacher/Qwen3.8-27B-heretic-ara-GGUF) ship with the MTP/NextN layer as an extra block (block_count65), which today's stock llama.cpp / Ollama can't load — the loader treats the NextN block as a hybrid layer and aborts withmissing tensor 'blk.64.ssm_conv1d.weight'(verified on llama.cpp + Ollama 0.30.8). SoThanatos-27B.Q4_K_M.ggufis now the heretic-ara Q4_K_M run throughscripts/strip_mtp.py(drop block 64 →block_count64, 851 tensors; alreadyqwen35-stamped, no re-stamp). Verified:verify_archgreen, and it loads + generates on llama.cpp (Vulkan, 8060S iGPU). Benchmarks carry over (same architecture + quant size), pending a re-measure withmake bench(superseded later in this release: they do not carry over; see the throughput entry).
Added
/v1cap-ctx helper + strip_mtp/build.sh hardening. Newscripts/cap_ctx.sh(wrapped asmake cap-ctx) bakes a small-num_ctxlocal tag (num_ctx→4096,num_batch256, reusing the Modelfile's TEMPLATE/SYSTEM) so OpenAI/v1clients — which can't override the bakednum_ctxand OOM on small hosts — work in one command (verified: the tag loads via/v1at CONTEXT 4096, no OOM). Also hardenedscripts/strip_mtp.pyto refuse stripping unless the target block actually holds a NextN tensor (guards against dropping a real transformer layer on a malformed upstream), and documented the.stripped.ggufsibling inbuild.sh's header.- Header spec line now surfaces context length. Added a
Context: 262,144 native / 1,010,000 extensiblefield to the card's header spec row (alongside Architecture / Parameters / Base / …), so the native + YaRN- extensible window is visible at a glance. Thenum_ctx 1010000default and the Architecture / Hardware detail are unchanged. scripts/strip_mtp.py— MTP/NextN layer stripper forqwen35GGUFs. Drops the extrablk.<last>.*MTP block, setsblock_countback to 64, and removesnextn_predict_layers; kept tensors are copied byte-for-byte (no re-quant). Needed because current heretic-ara GGUF conversions bundle the MTP layer, which stock llama.cpp / Ollama can't load — see the base-rebase entry above. It is a conditional no-op (hardlink, no extra disk) on already-clean quants, andscripts/build.shnow runs every fetched quant through it, somake build QUANT=<any>produces a loadable model — not only the bundled Q4_K_M. Companion toscripts/rename_arch.pyin the GGUF-surgery toolset.scripts/check_bridge_sync.pynow guards the example clients' system prompt (ported from the MoE siblingFoolDev/Janus-35B-HERETIC). Beyond the Modelfile↔bridge check, it parses eachexamples/*.pyand asserts any module-level*_SYSTEMconstant matches the rootsystemverbatim; the vision client is skipped (deliberate image-specific prompt). Wired intoscripts/check.shand the pre-commit hook, so drift is caught before it lands.- Frontmatter tags
reasoning+distillation. The card is positioned reasoning-first and distilled (andCITATION.cffalready lists both as keywords), but neither was a searchable Hub tag — added both. Parity with the Janus sibling. CITATION.cffnow carriesversion+date-released. Added0.1.0/2026-05-02(mirroring the CHANGELOG release anchor) so the HF "Cite this model" widget generates a dated, versioned citation. Parity with the Janus sibling.
Changed (default sampling matched to the Fable teacher)
- Default sampling raised to a Fable-matched profile in
paramsand theModelfile(bridge-synced):temperature 0.6 → 1.0,top_k 20 → 0(Claude uses no top-k).top_p 0.95andrepeat_penalty 1.05are kept as guardrails against reasoning-loop degeneration (open weights + a forced<think>block need them, unlike Claude's sampler). In parity with the MoE siblingFoolDev/Janus-35B-HERETIC. The former 0.6/0.95/20/1.05 profile remains in the README "Recommended sampling" table as the tighter-reasoning override.
Changed (context-length ceiling raised to Qwen's 1,010,000 extensible max)
- Advertised context is now the precise 262,144-native / 1,010,000-extensible
pair, and the baked default rises to match (parity with the MoE sibling
FoolDev/Janus-35B-HERETIC). The card previously rounded the extensible window to~1 Mand bakednum_ctx 1000000(a Fable-teacher budget match); both now use the exact Qwen 3.6 figures — 262,144 native, extensible to 1,010,000 tokens with YaRN. The baked default moves1000000 → 1010000inparamsand theModelfile(bridge-synced), the opt-in YaRN recipe is retuned to--rope-scale 3.853(≈1010000 / 262144) at-c 1010000, and the theoretical KV footprint at the default is recomputed to ~62 GB.
Fixed
- Post-rebase audit fixes (multi-agent card audit). (1)
examples/llama_cpp_vision.py--mmprojhelp named a non-existentmmproj-BF16.gguf— a bare reference the rebase sed missed (the heretic-ara repo publishes onlymmproj-f16/mmproj-Q8_0), nowmmproj-f16.gguf. (2)examples/README.mdlistedQ3_K_M ~12 GB(that's the Q3_K_S size; Q3_K_M is13 GB everywhere else) → `13 GB. (3) TheModelfilebench-provenance note still saidrebundle pendingafter the rebundle shipped → aligned to the README's "figures carry over — re-measure withmake bench`". - Ignore
build.sh's*.stripped.ggufintermediates. Amake build GGUF_PATH=<bundled blob>run producesThanatos-27B.Q4_K_M.stripped.gguf, which the broad!Thanatos-27B.*.ggufun-ignore rule left trackable; re-ignored it so it can't be accidentally committed. (Verifiedmake buildend-to-end while here.) - OpenAI
/v1examples now warn about thenum_ctxOOM on small hosts./v1/chat/completionshas nonum_ctxknob, so it loads at the bakednum_ctx 1010000and OOMs on ≤~48 GB hosts (the KV cache alone wants ~62 GB) — but the examples showed/v1with no caveat. Added a callout beside the OpenAI-compatible examples: use/api/chatwith"options": {"num_ctx": 4096}, or/savea small-context local tag for OpenAI clients (/v1can't pass the override inline). - Banner regenerated for the 3.8 rebase. The
banner.svgsubtitle still readQwen 3.6 Heretic(the card embeds the SVG, so this was visible on the live page); corrected toQwen 3.8 Hereticand re-renderedbanner.pngfrom it (rsvg-convert, 766×154).dense-flow.svg/bmc-banner.svgcarry no version text, so they and their renders are unchanged. llama_cpp_quickstart.pyCPU-only example no longer passes--gpu-layers 99(parity with the Janus sibling). Theexamples/README.mdllama-cpp-python block installed the CPU-only wheel but then ran with--gpu-layers 99— a silent no-op on that build; the flag moved into the GPU-rebuild note.- Modelfile footprint math now agrees with the README and the spec table.
The
Modelfilehardware comment still carried the pre-reconciliation29 GB weights+compute-graph anchor (91 GBcompute graph alloc ~12 GB; `/45 GB12 →totals) while the README hardware paragraph and the Janus-vs-Thanatos comparison table (Min host memory @ Q4 / 8K ctx | ~22 GB) had already moved to ~22 GB. Aligned the Modelfile to the ~22 GB anchor (graph5 GB84 GB; totalsat the default /~38 GBat 262144 native), completing the 2026-08-02 reconciliation (08ae542`) that had missed this one file. - Example accuracy brought in line with the Janus sibling. Three lagging
spots corrected:
llama_cpp_vision.py's--mmprojhelp saidmmproj-F16.gguf(the rest of the file and Janus say BF16);ollama_chat.py's docstring said "Current Ollama (0.24" (its own body and Janus say "0.24+"); and thetransformersinstall floor was>=4.45in three places, below the Qwen 3.6 support floor Janus pins at>=4.51. - Inference examples no longer 404 for HF-pull users. The OpenAI-compatible
examples hard-code
model: "thanatos-27b", the tag from the local build; a user who pulled via the TL;DR one-liner (hf.co/FoolDev/Thanatos-27B-HERETIC) hitmodel not found. Added a note to substitute the full tag (orollama cp). Parity with the Janus sibling. - Ollama vision-bug citations point at the open tracker. Five live surfaces
(README loader table,
examples/README.md,examples/llama_cpp_vision.py,scripts/fetch_vision.sh, and theModelfileheader) citedollama/ollama#15898, which was closed as a duplicate — readers clicking through saw "Closed" and could assume vision was fixed. All five now cite the open#14575. Parity with the Janus sibling. - Hardware footprint figures reconciled. The comparison table gave min host @ Q4/8K as ~22 GB while the Hardware section anchored weights+graph at ~29 GB (→ ~90 GB at 1M, ~45 GB at native) — a ~7 GB internal contradiction for the same config. Settled on the ~22 GB anchor (defensible for a ~17 GB GGUF): ~83 GB total at the 1M default, ~38 GB at the 262144 native window.
examples/ollama_chat.pytool demo no longer crashes on an omitted arg.fake_weather(city, unit)had no default forunit, so an Ollama tool call that omits the "required" arg raisedTypeErrormid-round-trip. Addedunit="celsius"(parity with the Janus sibling).scripts/rename_arch.pydefaults point the recovery direction. Defaults were--from-arch qwen35 --to-arch qwen36(forward), opposite every caller (all pass qwen36→qwen35); a flagless run would mis-stamp the shipped qwen35 bundle to qwen36 and break loading. Flipped to qwen36→qwen35.- Hardware/eval numbers no longer self-contradict. The "No formal
evaluation" note said "Numbers above are estimates," but the
make benchtok/s figures (~12.3 Q3_K_S / ~9.3 Q4_K_M) are measured — reworded to carve those out. Parity with the Janus sibling. examples/README.mdestablishes its working directory. The Setup section's first runnable command assumed acd examplesthat was never stated (a fresh clone lands at repo root); added the note.- Loader-table Ollama version reads
0.24+. The lone bareOllama 0.24cell now matches the open-ended0.24+used elsewhere. scripts/load_bundle.sh+scripts/heal_hf_pull.shsize probes are portable. Both sized files with GNU-onlystat -c '%s'(errors on macOS/BSDstat, which needs-f%z) — switched towc -c, identical on GNU and BSD. Parity with the Janusload_bundle.shfix.examples/llama_cpp_quickstart.pysystem prompt restored. ItsTHANATOS_SYSTEMhad truncated to five behavior rules (dropped "or meta-commentary" and "or incomplete", and omitted the creative-writing and analysis/technical-help rules), diverging from the rootsystem. Restored to the full seven-rule prompt, byte-identical tosystem. Parity with the Janus sibling.examples/README.mdrefreshed. It claimed all four clients share the system prompt and the0.6/20sampling "defaults"; neither held —llama_cpp_vision.pyuses a vision-specific prompt, and the shipped default is now Fable-matchedtemp 1.0 / top_k 0, soollama_chat.pyinherits1.0/0while the non-Ollama scripts pin the tighter0.6/20explicitly. States which profile each path actually uses (parity with the Janus fix).scripts/check_bridge_sync.pycasts params by value shape, not a key whitelist.parse_modelfileonly numerically cast a hardcoded set of keys, so any other numeric param (e.g.min_p,presence_penalty) stayed a str and tripped a false "params drift" against its JSON number. Now casts every non-stop valueint()thenfloat()(mirroringjson.loads); ported from the Janus sibling.think=falsenow suppresses reasoning. The chat template unconditionally prefilled an open<think>on the generation prompt, so requests passingthink: false(orollama run --think=false) still forced a reasoning trace that leaked intocontent. The prefill is now gated on$.IsThinkSet: a closed empty<think></think>when thinking is explicitly off, an open<think>otherwise. Verified on Ollama 0.30.8 (think=truekeeps reasoning in thethinkingfield,think=falsesuppresses it, unset still reasons);templatekept byte-synced with theModelfileTEMPLATE. In parity with the MoE siblingFoolDev/Janus-35B-HERETIC.scripts/smoke_test.shandscripts/bench.shno longer OOM at the 1M default. They sent nooptions, so they inheritednum_ctx 1000000and its ~60 GB KV allocation; they now pinnum_ctx: 4096, num_batch: 256(the documented constrained-host config).
Changed (default context window 262144 → 1000000, a ~1M advertised ceiling)
num_ctxdefault raised 262144 → 1000000 inparamsand theModelfile(bridge-sync kept in sync), in parity with the MoE siblingFoolDev/Janus-35B-HERETIC. This targets a ~1M ceiling above the 262 144 native trained window. The bundled GGUF has no YaRN rope-scaling (rope.freq_base 10M, norope.scaling), so positions past ~262K use untrained RoPE and degrade — 1M is a capability-first advertised ceiling, not a coherent context.num_ctxstays a per-run ceiling, not a floor, and KV grows to ~61 GB at 1M (q8_0, ~2 GB/32K → ~90 GB total; theoretical, extrapolated), so memory-constrained hosts must overridenum_ctxdown at load (the 262 144 native window → ~16 GB KV / ~45 GB total;num_ctx 32768→ ~2 GB KV). The Z13 measured data point stays at its as-benchednum_ctx 16384; native-context checks (verify_arch.py) stay at 262 144 (the GGUF's trained window).
Changed (default context window 16384 → 262144, the native max)
num_ctxdefault raised 16384 → 262144 inparamsand theModelfile(bridge-sync kept in sync), matching the MoE siblingFoolDev/Janus-35B-HERETIC— the model's full native context. The old 16K window exhausted mid-conversation on a reasoning-distilled model that emits long<think>traces (triggering Ollama context-shift); the default is now capability-first — the whole 262 144-token window instead of a conservative slice.num_ctxis a per-run ceiling, not a floor, so memory-constrained hosts override it down at load (e.g.num_ctx 32768→ ~2 GB KV). At the 262K default, KV cache is ~16 GB (q8_0) for a ~45 GB theoretical footprint, so most hosts must trim. The Z13 measured data point stays at its as-benchednum_ctx 16384. (This default passed through 32768 before landing at the native max.)
Changed (HF repo rename → FoolDev/Thanatos-27B-HERETIC)
- HF repo renamed
FoolDev/Thanatos-27B→FoolDev/Thanatos-27B-HERETICvia the HF UI. HF serves a 307 redirect from the oldFoolDev/Thanatos-27Bslug to the new canonical name, so existing pulls keep resolving. The model's display name staysThanatos-27B— only the repo slug / URLs change. - Repointed self-referential slugs to the new name — README
banner /
bmc-banner/dense-flowimage URLs, theollama run/ollama pull/git clone/hf downloadquick-start and local-apps rows,CITATION.cff(url:+repository-code:), and the functional script defaults (heal_hf_pull.shTAG,load_bundle.shREPO_ID,heretic_remote.shMODEL,smoke_test.shusage,check_bridge_sync.py,Makefilehelp,.gitignorecomment,examples/*) all now point atFoolDev/Thanatos-27B-HERETIC. Theheretic_remote.shREPO_IDupload target (FoolDev/Thanatos-27B-Heretic-v2) is unchanged. Historical CHANGELOG / README notes that record the pre-renameFoolDev/Thanatos-27Bslug are left verbatim. - Repointed the MoE-sibling cross-links (README sibling badge,
intro link, comparison-table header, "Related models" row, and
CITATION.cffreferences entry) toFoolDev/Janus-35B-HERETIC, and set the git remote togit@hf.co:FoolDev/Thanatos-27B-HERETIC.
Changed (base swap → llmfan46 Heretic v2)
- Swapped the immediate base from
Qwen/Qwen3.6-27B(vanilla) →llmfan46/Qwen3.6-27B-uncensored-heretic-v2— an uncensored "Heretic" abliteration of the same dense Qwen 3.6 27B arch. It isqwen35-stamped like vanilla Qwen 3.6 27B, so the Architecture section, theconfig.jsondiscussion, and the bundled blob's loadability are all unchanged. Base-lineage swap only: the repo nameThanatos-27B, thethanatos-27bOllama tag, theFoolDev/Thanatos-27BURL, and the Claude Fable 5 teacher slot are all kept — this is not the repo rename that accompanied the earlier (reverted) attempt.- README: frontmatter
base_model:+base_model_relation: finetune;heretic/uncensoredtags added (library_name: transformerskept). Base badge, tagline, fact line (Type: Distilled + Abliterated), sibling paragraph, bundled-blob callout, MTP section, Related-models table, and Credits reframed to the Heretic v2 lineage;Qwen/Qwen3.6-27Bis still referenced as the upstream pre-Heretic base. - Tooling:
scripts/build.shREPO_ID→llmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUF, filename patternQwen3.6-27B-uncensored-heretic-v2-${QUANT}.gguf, andQ3_K_S→Q3_K_Mfor buildable-quant references (the Heretic GGUF repo publishes no Q3_K_S).scripts/fetch_vision.sh→ BF16Qwen3.6-27B-mmproj-BF16.gguffrom that repo (unsloth'smmproj-F16.ggufkept as a documented fallback projector).Modelfile,CITATION.cff, andexamples/*flipped to match;check.shfilename guard comment updated. DefaultTAGstaysthanatos-27b. banner.svgsubtitle → "Qwen 3.6 Heretic · Dense 27B · Fable 5";banner.pngre-rasterized from the updated SVG (unchanged 766×154, 2×).- Bundled
Thanatos-27B.Q4_K_M.ggufrebundled — replaced the legacy pre-Heretic Qwen quant with the Heretic v2 Q4_K_M fromllmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUF(qwen35-stamped; verified against the README Architecture claims viamake verify-arch). Pullinghf.co/FoolDev/Thanatos-27Bnow serves the uncensored Heretic v2 behavior directly, no local build required.
- README: frontmatter
Added (heretic abliteration tooling)
scripts/heretic_remote.sh+scripts/heretic_unattended.py— submit an unattendedheretic-llmabliteration run tohf jobs(the local box has no GPU / not enough RAM for a 27B abliteration).heretic_unattended.pymonkey-patchesheretic.main's interactive prompts to pick the first Pareto-front trial and push to HF headlessly (heretic-llm has no batch mode); pinned to a specificheretic-llmversion by the caller.heretic_remote.shbase64-ships the driver into anhf jobs run(dry-run by default;SUBMIT=1to actually submit). Default upload target isFoolDev/Thanatos-27B-Heretic-v2— not the bare-Hereticslug, which 307-redirects onto this base repo and would clobber it.
Changed (support badge → Tokyo Night banner)
- Replaced the shields.io "Buy me a coffee" badge in the README badge
row with a custom
bmc-banner.svgsupport banner, themed to matchbanner.svg(same Tokyo Night gradient, monospace wordmark, warm#e0af68accent; animated rising steam, a pulsing hot-cup glow, a sweeping warm scan beam, a slow shimmer on "coffee", and a blinking cursor). Links to the unchangedbuymeacoffee.com/cardoffoolm. Addsbmc-banner.svg; no other badge or link changed. Mirrors the same banner inFoolDev/Janus-35B.
Fixed (post-revert / post-relabel drift cleanup)
.gitignore— stalehf.co/FoolDev/Thanatos-27B-Hereticslug in the "Use this model" comment corrected tohf.co/FoolDev/Thanatos-27B; the bulk rename-back missed this one comment.Modelfile— dropped the orphaned(Heretic v2 base is not benched here yet; rebundle pending.)benchmark comment. The base was reverted to vanillaQwen/Qwen3.6-27B, so the surrounding tok/s figures are already the current vanilla-base numbers and no Heretic rebundle is pending.scripts/smoke_test.sh— the "model not found" build hint describedload_bundle.shas loadingthis repo's qwen36 bundle; the shipped bundle isqwen35-stamped (canonical sincee03e10e), so the hint now saysqwen35.README.md— "Why a 27B variant?" quoted the dense 27B at Q3_K_S as~10 tok/s, the superseded ROCm snapshot. The Hardware-requirements reference section already lists the current Vulkan measurement (12.3 tok/s) as the winner, so the intro now reads `12 tok/s`.
Changed (teacher label — Opus 4.7 → Fable 5)
- Reasoning-teacher references relabeled from
Claude Opus 4.7toClaude Fable 5across the model card (TL;DR, architecture line, Janus-35B sibling note, and Credits),banner.svgsubtitle, andCITATION.cff. The external dataset slugcrownelius/Opus-4.7-Reasoningin the metadata is left unchanged — it is a real Hugging Face dataset path, not a teacher label. Mirrors the same relabel inFoolDev/Janus-35B.
Changed (project name reverted: Thanatos-27B-Heretic → Thanatos-27B)
- HF repo renamed back to
FoolDev/Thanatos-27Bvia the HF UI (HF serves a 307 redirect fromFoolDev/Thanatos-27B-Hereticto the canonical name). Now that the base is also reverted to vanillaQwen/Qwen3.6-27B, the-Hereticsuffix had no remaining justification. - Bulk-renamed
Thanatos-27B-Heretic→Thanatos-27Bandthanatos-27b-heretic→thanatos-27bacross README, Modelfile, scripts, examples, CITATION.cff, Makefile, .gitignore. CHANGELOG history entries below this one retain their original wording (they document actions taken under the Heretic name, before this revert). - Banner wordmark — dropped the
-HERETICtspan from banner.svg, leavingTHANATOS-27B. banner.png re-rasterized at 2× via rsvg-convert. - README "Note on the name" callout removed — no longer applicable since name and base are aligned again.
- CITATION.cff abstract — dropped the trailing parenthetical about the reverted Heretic swap.
- Local git remote re-pointed from
git@hf.co:FoolDev/Thanatos-27B-Heretictogit@hf.co:FoolDev/Thanatos-27B.
Reverted (base swap to Heretic v2 — name kept, base back to vanilla Qwen)
- Undone the
Qwen/Qwen3.6-27B→llmfan46/Qwen3.6-27B-uncensored-heretic-v2base swap that shipped in16e1dddand was polished in subsequent commits. Current base is back to vanillaQwen/Qwen3.6-27B. README frontmatterbase_model:, theBase-…badge, the Architecture line, the sibling paragraph, the Quick-start path C, the Local-apps table, the Vision section, the Related-models table, the Credits, and the Known-limitations section all flipped back to the pre-swap Qwen-only framing.heretic/uncensoredtags removed (imatrixstays — the bundled blob is still iMatrix-quantized regardless of which base is described).base_model_relation: finetuneremoved; this is a packaging wrapper, not a finetune. - Tooling flipped back to unsloth's GGUF mirror.
scripts/build.shREPO_IDback tounsloth/Qwen3.6-27B-GGUFwith filename patternQwen3.6-27B-${QUANT}.gguf; quant list back to the unsloth catalog (Q3_K_S restored as the smallest practical quant).scripts/fetch_vision.shdefaults back toPRECISION=F16andmmproj-F16.gguffrom unsloth. Modelfile preamble flipped.examples/transformers_quickstart.pyMODEL_IDback toQwen/Qwen3.6-27B.examples/README.mdandexamples/llama_cpp_vision.pyrecipes flipped.CITATION.cfftitle, abstract, references, and keywords flipped.Makefilehelp-text +builddocstring flipped..gitignorecomments- whitelist + rebadge-artifact glob flipped.
banner.svgsubtitle revertedDense 27B · Opus 4.7 distilled · uncensored→Qwen 3.6 · Dense 27B · Opus 4.7 distilled.THANATOS-27B-HERETICwordmark kept — the project name string and HF repo URL are preserved per explicit choice ("undo base only, keep name").banner.pngre-rasterized at 2× via rsvg-convert.- Project name string
Thanatos-27B-Hereticand Ollama tagthanatos-27b-hereticretained across all files. HF repo URL stays atFoolDev/Thanatos-27B-Heretic; git remote unchanged. A "Note on the name" callout added to the README tagline explaining the name-vs-base mismatch so users aren't surprised. - Bundled blob unchanged (
Thanatos-27B.Q4_K_M.ggufLFS pointer SHA5ed60d0a...). It was always the legacy unsloth Qwen Q4_K_M quant; with the base reverted, the blob and the declared base are now consistent again. The "Bundled blob status" callout in TL;DR removed since it no longer applies. - HF repo migration: the HF repo at
FoolDev/Thanatos-27B-Heretickeeps its current name (the user's earlier rename via HF UI stands). If you want to also rename the HF repo back toFoolDev/Thanatos-27B, that's a separate HF UI action — HF will serve a 307 redirect from the new name to the old once renamed.
Changed (acknowledge HF's imatrix auto-tag in frontmatter)
- Added
imatrixto the READMEtags:list. HF's tag auto-detector was surfacingimatrixon the rendered model card (from the bundled Q4_K_M GGUF's metadata — the legacy unsloth-derived blob was iMatrix-calibrated) even though the frontmatter didn't declare it. Same "auto-tag surprise" pattern that motivated the earliergeneral.tagsstrip (9cc78e7) and theconfig.jsondrop (5302d10). Acknowledging it in the frontmatter makes the explicit tag list the source of truth instead of fighting the detector — the bundled blob is in fact iMatrix-quantized, so the tag is accurate.
Changed (post-rename polish — banner, gitignore, Makefile)
banner.svgtitle updatedTHANATOS-27B→THANATOS-27B-HERETIC(added-HERETICtspan in Tokyo Night red#f7768e, matching the abliteration vibe). Subtitle updatedQwen 3.6 · Dense 27B · Opus 4.7 distilled→Dense 27B · Opus 4.7 distilled · uncensored. (Initial post-rename subtitle wasHeretic v2 · Dense 27B · Opus 4.7 distilled · uncensored, but rasterization showed it overflowed the 383px viewBox and truncateduncensored→unc. Dropped theHeretic v2 ·prefix since the-HERETICwordmark already conveys the lineage; the v2 specifier lives in the README badges and frontmatter.) ViewBox unchanged (383×77).banner.pngre-rasterized frombanner.svgat 2× density (766×154) viarsvg-convert -w 766 -h 154. Previous PNG was the pre-renameTHANATOS-27Bsnapshot from 2026-05-16 at 1× (383×77, 9.8 KB); new PNG is 24 KB. Used as a fallback for tools that don't render SVG; the README itself loadsbanner.svgdirectly atwidth="100%".dense-flow.pngadded as a fallback sibling todense-flow.svg, rasterized at 2× density (1600×480, 78 KB) viarsvg-convert -w 1600 -h 480. Captures a single static frame of the animated forward-pass visualization (the SVG shows all 64 layer-bars at baseline opacity with the left-to-right pulse glow at its starting position). README itself loadsdense-flow.svgdirectly; PNG is for non-SVG renderers. File table entry added under "What's here".
Fixed (Heretic v2 GGUF size quotes)
- README quant-size figures corrected against actual files at
llmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUF(HEAD requests against the LFS-redirected URLs): Q3_K_M actual 13.30 GB (was quoted~12 GBin two TL;DR-style spots); Q5_K_M actual 19.23 GB (was quoted~20 GB/20 GBin two places). Updates:README.md"For other quants" line and themake build QUANT=Q5_K_Mcomment in Quick start;examples/README.mdSetup/Ollama section. Q4_K_M17 GB quote (actual 16.55 GB) rounds correctly and was left alone. The more detailed references in the architecture table, hardware table, and13 GB` for Q3_K_M; this aligns the rest of the doc with those.make build QUANT=Q3_K_Mcomment already used ` .gitignorecomments refreshed to mention llmfan46 Heretic v2 GGUF source and the canonicalhf.co/FoolDev/Thanatos-27B-HereticOllama URL. Whitelist extended with!Thanatos-27B-Heretic.*.ggufand rebadge-experiment block extended withThanatos-27B-Heretic.*.qwen[0-9]*.ggufso the pending Heretic rebundle is already accommodated.Makefilehelp-text andbuilddocstring updated to reference the Heretic v2 GGUF source (llmfan46/...) and useQ3_K_Min examples (noQ3_K_Sin the Heretic repo).cleantarget now also removes legacyThanatos-27B.*.qwen[0-9]*.ggufrebadge artifacts (theThanatos-27B.Q4_K_M.qwen35.gguf/Thanatos-27B.Q4_K_M.qwen36.gguffiles from the pre-rename stamp saga that still sit in working trees).
Changed (project rename + base swap to Heretic v2)
- Renamed project
Thanatos-27B→Thanatos-27B-Hereticand swapped immediate base fromQwen/Qwen3.6-27B(vanilla) →llmfan46/Qwen3.6-27B-uncensored-heretic-v2(an uncensored Heretic-style abliteration of the dense Qwen 3.6 27B base). README, Modelfile preamble,CITATION.cff, all scripts, and all examples now refer toThanatos-27B-Heretic/thanatos-27b-heretic(lowercase Ollama tag) and pull GGUFs fromllmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUF. Architecture is unchanged (still Qwen 3.6 dense 27B,qwen35-stamped, hybrid SSM+attention stack) — only the weights' finetune lineage moves. (Initial docs commit16e1dddused the word orderThanatos-Heretic-27B; corrected here to match the actual HF repo nameFoolDev/Thanatos-27B-Hereticthe user created via the HF rename UI.) base_model:frontmatter flipped tollmfan46/Qwen3.6-27B-uncensored-heretic-v2;base_model_relation: finetuneadded;hereticanduncensoredtags appended.library_name: transformersstays for HF Hub placement (snippet trap accepted as before;config.jsonis still intentionally absent).scripts/build.shnow pointsREPO_IDatllmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUFand uses the filename patternQwen3.6-27B-uncensored-heretic-v2-${QUANT}.gguf. DefaultTAGisthanatos-27b-heretic. Note: noQ3_K_Sin the Heretic GGUF repo — useQ3_K_Mfor the smallest practical quant (Modelfilepreamble and README hardware/quick-start tables updated accordingly).scripts/fetch_vision.shdefaults flipped toPRECISION=BF16andREPO_ID=llmfan46/Qwen3.6-27B-uncensored-heretic-v2-GGUF(Qwen3.6-27B-mmproj-BF16.gguf, ~931 MB). Unsloth'smmproj-F16.ggufis documented as a reference fallback for users who want the F16/F32 variants.- Bundled blob status: the in-repo
Thanatos-27B.Q4_K_M.ggufLFS pointer is unchanged — still the legacy pre-Heretic Qwen 3.6 27B Q4_K_M quant (5ed60d0af4650a854b1755bd392f9aef4872643dc25a254bc68043fa638392a0). Behaves identically to vanilla Qwen 3.6 27B for now. Heretic v2 re-quantization + rebundle (file rename toThanatos-27B-Heretic.Q4_K_M.gguf+ LFS swap) is a separate follow-up; users wanting actual Heretic behavior today should use the local-build path (make build). - HF repo migration: the HF repo was renamed by the user from
FoolDev/Thanatos-27BtoFoolDev/Thanatos-27B-Heretic(HF serves a 307 redirect from the old path). Local git remote re-pointed fromgit@hf.co:FoolDev/Thanatos-27Btogit@hf.co:FoolDev/Thanatos-27B-Hereticto follow. - CHANGELOG history left intact: entries below this one still
reference
Thanatos-27Band the bundled-blob saga as they happened on the old repo identity. Historical, not retconned.
Changed (HF tag-surface cleanup — general.tags strip + config.json drop)
- Stripped
general.tagsKV from the bundled GGUF (9cc78e7, 2026-05-20). Drops the upstream-bakedunslothandimage-text-to-texttags thatllama.cpp's converter copies into GGUFs fromunsloth/Qwen3.6-27B-GGUF; both surfaced on the HF model page and obscured this card's positioning. Tensors byte-identical; only thegeneral.tagsKV is gone. - Dropped
config.json(5302d10, 2026-05-20) to suppress HF's tag auto-detector surfacingqwen3_5in the repo header — the detector readsarchitecturesfromconfig.json. Consequence:AutoModelForCausalLM.from_pretrained( "FoolDev/Thanatos-27B")no longer works on its own.examples/transformers_quickstart.pyand the README transformers note now point users at upstreamQwen/Qwen3.6-27Bdirectly (tensors byte-identical, so the result is the same model).library_name: transformersstays in the model-card metadata for Hub placement.
Reverted (safetensors mirror experiment)
- Mirrored Qwen/Qwen3.6-27B's safetensors set into this repo
(
b420378, 2026-05-20), reverted within the day (50f66849cf363e, 2026-05-21). 15 sharded.safetensors+ tokenizer- processor configs (~58 GB) were briefly added so users
wanting GGUF + safetensors in one place could skip a second
hf download; reverted on reflection. Transformers users continue to pull from upstreamQwen/Qwen3.6-27B..gitignorewhitelist for the Qwen sharded naming pattern (0c5bee4) was removed alongside the mirror;*.safetensorsblock rule is back to baseline.
Changed (5th round trip — qwen36 → qwen35, retested next-day)
- Bundle re-stamped
general.architecture: 'qwen36'→'qwen35'inhf uploadcommite03e10e(HF), 2026-05-20 midday — 8 minutes after a brief re-flip to qwen36 (ae67ed1) at the start of a fresh next-day session. Tested the friction once more in a new session; conclusion unchanged from the 4th round trip. Bundle blob SHA back to5ed60d0af4650a854b1755bd392f9aef4872643dc25a254bc68043fa638392a0(byte-identical tensors across all qwen35 stamps). - README "Architecture > History" intro count goes from six
flips to eight; bullets gain 7th (
ae67ed1) and 8th (e03e10e) entries; the "current state" marker moves toe03e10e. README TL;DR heal-hf note, Modelfile preamble,scripts/load_bundle.sh,scripts/heal_hf_pull.sh, andexamples/README.mdupdated to referencee03e10eas the latest qwen35 re-stamp.
Changed (4th round trip — qwen36 → qwen35, friction concretely tested)
- Bundle re-stamped
general.architecture: 'qwen36'→'qwen35'inhf uploadcommit978798f(HF). Fourth stamp flip on the 2026-05-19 session, this one resolving the saga on the upstream-canonicalqwen35side after the 3rd round trip (973d7ef) had its friction empirically tested. Seven sequential fresh-pull → heal-hf cycles on Strix Halo tonight made the friction concretely-experienced rather than hypothetical: every cycle worked (the heal flow is solid and deterministic), every cycle was an unnecessary obstacle for users who just wantollama runto work first try. The audit (a4d3b6e) called the canonical stamp correctly; the practical friction outweighed the version-specificity payoff. New bundle blob SHA is5ed60d0af4650a854b1755bd392f9aef4872643dc25a254bc68043fa638392a0(same as the post-2nd-round-trip72259c1and post-1st-round-trip964e418qwen35 blobs — byte-identical tensor data across all qwen35 stamps). - README "Architecture" section collapsed back to the qwen35-
canonical narrative: the three-layer evidence (Qwen's HF
configs, llama.cpp's converter, llama.cpp's model code) stays
intact; the "Stamp choice" subsection (which framed qwen36 as
a deliberate project preference) is removed; History picks up
a 6th-flip bullet documenting the
978798frevert and what finally made the friction tradeoff decisive. - README "Heads up" callout removed from TL;DR; one-liner
ollama run hf.co/FoolDev/Thanatos-27Brestored as the canonical path. The legacy heal-hf recovery note stays (for users with qwen36-stamped blobs from either of the two qwen36 windows still in their store). - README "Local apps" intro + Ollama / llama.cpp table cells
flipped back to "loads directly". The llama.cpp recipe is
back to
hf download FoolDev/Thanatos-27B Thanatos-27B.Q4_K_M.gguf --local-dir . && llama-server -m Thanatos-27B.Q4_K_M.gguf— the qwen35 stamp makes the bundled file directly loadable, no rebadge step needed. Modelfile,scripts/load_bundle.sh,scripts/heal_hf_pull.sh,examples/README.md, andMakefilepreambles / help text / examples README intros all flipped back to describing the qwen35 stamp as current state. The rebadge code paths in load_bundle.sh and heal_hf_pull.sh stay — both have explicit legacy-recovery branches that no-op on qwen35-stamped bundles but rebadge on encountering a qwen36-stamped one (pre-964e418v0.6.0-era pulls, or pulls in the 3rd-round-trip window between973d7efand978798f).scripts/rename_arch.pyexample in the README's Rebadge utility subsection flipped back to qwen36 → qwen35 (the actually-useful direction now — qwen35 → qwen36 was only ever needed to produce the now-reverted bundles).
Note on the saga
- Eight total stamp flips across 2026-05-19/20: qwen35 →
qwen36 (v0.6.0) → qwen35 (
964e418) → qwen36 (07fa120) → qwen35 (72259c1) → qwen36 (973d7ef) → qwen35 (978798f) → qwen36 (ae67ed1) → qwen35 (e03e10e). The repo is now firmly on the upstream-canonicalqwen35stamp; the audit evidence (under "Architecture" in the README) is the durable artifact of why. The history bullets document the path. If a future maintainer wants to test the version-specific stamping again, the rebadge tools (make load-bundle,make heal-hf,scripts/rename_arch.py) are all still wired up to do it — the lesson is in the friction, not in the absence of tooling.
Validated (2026-05-19 evening, post-973d7ef)
Empirical confirmations that the qwen36 stamp + heal-hf recovery is operationally solid, captured here so they're not just lost to chat scrollback:
- Heal-hf reproducibility — 5 cycles. Ran the full
ollama rm→ freshollama run(qwen36 500) →make heal-hfloop five times in sequence on this box (ROG Flow Z13, Strix Halo). Every cycle produced the same healed blob digestsha256:5ed60d0af4650a854b1755bd392f9aef4872643dc25a254bc68043fa638392a0(byte-identical to the pre-2nd-round-trip qwen35 stamp). The rebadge script (scripts/heal_hf_pull.shviamake heal-hf) is deterministic across pulls, and the qwen35 blob it produces matches the upstream-canonical stamp. The friction is bounded: one error message per fresh pull, one command to fix, no follow-on issues. - Tool calling end-to-end on the healed tag. Ran
MODEL=hf.co/FoolDev/Thanatos-27B:Q4_K_M make smoke-toolsafter the 1st heal cycle. Server reachable, model present, capabilities includedtools(HF Ollama bridge wired.Tools/.ToolCallsfromtemplate), plain round-trip returnedOK, tool-call round-trip returned a structuredtool_callsarray withname=get_weather,city=Tokyo. Confirms the bridge files (template/system/params) are not collateral damage from the rebadge — Ollama's capability detector still finds the right hooks. - Architecture metadata still matches README claims. Ran
make verify-archagainst bothThanatos-27B.Q4_K_M.qwen35.gguf(the source) andThanatos-27B.Q4_K_M.qwen36.gguf(the current HF bundle); all 13 quantitative bullets verified clean on each stamp. No silent metadata drift across thescripts/rename_arch.pyflip. - Performance unchanged across stamps. Q4_K_M Vulkan bench
on the healed qwen36 tag came in at 9.32 tok/s aggregate
(run 3, commit
693cf65), within 0.1% of run 1's 9.31. The metadata-only rebadge produces an inference-path-equivalent model.
Added
scripts/verify_arch.py+make verify-arch. After correcting the MTP bullet (see Fixed below) it was worth confirming the remaining Architecture forward-pass bullets weren't carrying any other quiet inaccuracies. Cross-checked all thirteen quantitative claims (layer count, hidden / FFN dims, head counts, head_dim, RoPE factor, hybrid stack interval, SSM dims, vocab, context) against the actual GGUF metadata keys; every one verified clean on both the qwen35- and qwen36-stamped bundles. The script captures that mapping as a reusable artifact: pass it a path to a Thanatos-27B (or compatible) GGUF and it prints each bullet alongside the metadata key it derives from, exiting non-zero on mismatch. Useful after future re-stamps / re-conversions / upstream converter updates. Not folded intomake checkbecause it has to read the 17 GB tensor file (LFS smudge required) — run on demand.
Fixed
- README "Multi-token prediction (MTP)" bullet corrected. The
earlier wording — "MTP head trained for speculative decoding" —
was technically true about the upstream
Qwen/Qwen3.6-27Bsafetensors but misleading for the GGUF bundle this repo ships and for llama.cpp / Ollama users in general:- GGUFs are stripped.
convert_hf_to_gguf.pyexplicitly skips MTP tensors for theqwen35/qwen35moearch family ("MTP tensors are not used at inference yet; align with Qwen3Next behaviour"). Confirmed empirically viagguf.GGUFReaderon bothThanatos-27B.Q4_K_M.qwen35.ggufand the sourceunsloth/Qwen3.6-27B-GGUFQ4_K_M: both have 851 tensors and zero entries matchingmtp.*/draft.*/eagle.*/spec.*. Last tensor in either isblk.63.post_attention_norm.weight— the final layer norm, no MTP head after it. - Loader doesn't support it.
src/models/qwen35.cppandqwen35moe.cppcontain no MTP / nextn references; even if the tensors were in the GGUF, the loader wouldn't use them. - PR #22673's scope. llama.cpp's MTP support (merged
2026-05-16) was added for other architectures, not the
qwen35family. The README bullet now says so explicitly, points to vLLM (qwen3_next_mtp) and SGLang (--speculative-algo NEXTN) as the working consumer recipes against the safetensors, and notes that we're tracking the follow-up that would extend MTP to qwen35 / qwen35moe.
- GGUFs are stripped.
Changed (3rd round trip — qwen35 → qwen36, user-directed despite audit)
- Bundle re-stamped
general.architecture: 'qwen35'→'qwen36'inhf uploadcommit973d7ef(HF). Third stamp flip on the 2026-05-19 session, undertaken at project-owner direction after the upstream-evidence audit (a4d3b6e) had already shipped. The audit conclusion stands —qwen35is the upstream-canonical label for the whole Qwen 3.5 / 3.6 hybrid family, with concrete code-level evidence (Qwen's ownQwen3_5*ForConditionalGenerationHF classes, llama.cpp's converter mapping, the explicit Qwen 3.6 type branches inqwen35.cpp/qwen35moe.cpp). The project owner heard the evidence and chose the version-specific qwen36 stamp anyway, accepting themake heal-hffriction this creates on every freshollama run hf.co/FoolDev/Thanatos-27B. New bundle blob SHA isb2b63b941d714b0aff5e9afc9b337e7607b84e371ba991c182d92d0f7805c0e9; tensor data is byte-identical to the prior qwen35-stamped blob (5ed60d0a…), only thegeneral.architectureKV (and namespaced KV keys) flipped. - README "Architecture" section restructured into three subsections:
- Stamp choice — leads with the bundle's qwen36 declaration, states the tradeoff (loader friction in exchange for version-specific labeling) up front.
- Upstream convention — why
qwen35would also be valid — keeps the three-source audit evidence intact (Qwen HF configs, llama.cpp converter, llama.cpp model code) and explicitly says a future maintainer can flip back viamake load-bundle/make heal-hf. - History — logs all three flips on the 2026-05-19 session with commit refs and rationale per flip; the new entry annotates the choice as project-owner preference post-audit.
- README "Heads up" callout re-added to TL;DR (
ollama run hf.co/...500s on first inference;make heal-hfrebadges the cached blob). TL;DR ordering: heads-up callout → two-step pull-and-heal path → optional unsloth-based alternative for users who don't want to heal. - README "What's here" intro paragraph, "Local apps" intro, and Ollama table cell restored to the qwen36 / heal-hf-needed narrative.
Modelfile,scripts/load_bundle.sh, andscripts/heal_hf_pull.shpreambles flipped back to describing the qwen36 bundle as the current state and the rebadge as the primary use case (rather than legacy recovery).Makefileload-bundlehelp text + example comment restored to the "qwen36-stamped bundle, rebadged to qwen35" phrasing.
Note
- The audit shipped in
a4d3b6ewas not reverted. Its evidence remains accurate:qwen35is the upstream-canonical family label. The current stamp is a deliberate departure from that convention, not a refutation of it. A maintainer who later decides the friction isn't worth it can flip the bundle back to qwen35 with a singlemake load-bundleinvocation; the audit evidence already explains why that flip would be valid.
Changed (upstream-evidence audit — qwen35 is canonical, not a workaround)
in commit 964e418 (HF-direct hf upload). Reverses the v0.6.0
qwen36 stamp from e1f78fa, which itself flipped qwen35 → qwen36
deliberately. The proximate trigger: today's subagent recheck
found no PR or tracking issue for a qwen36 arch entry in either
ggml-org/llama.cpp or ollama/ollama, and the actual Qwen 3.6
family upstream already loads under qwen35moe — so the qwen36
stamp was going to stay ahead-of-upstream indefinitely, not for a
release or two. Day-to-day cost (every fresh
ollama run hf.co/FoolDev/Thanatos-27B hitting a 500, every user
needing make heal-hf to recover) outweighed the
architectural-honesty benefit. Tensor data stays byte-identical;
only the general.architecture KV (and the namespaced KV keys)
flips. New bundle blob SHA is
5ed60d0af4650a854b1755bd392f9aef4872643dc25a254bc68043fa638392a0
— the same hash make load-bundle and make heal-hf have been
producing locally this whole session.
- README "Heads up" callout (qwen36 load-failure warning) removed.
TL;DR collapsed back from "three paths around it" to a single
one-liner:
ollama run hf.co/FoolDev/Thanatos-27Bnow loads on stock Ollama.make heal-hfis mentioned as the recovery path for anyone who pulled the v0.6.0 bundle and still has the qwen36 blob in their store. - README "Architecture" section, Quick start option A,
examples/README.mdOllama setup, andModelfilepreamble all collapsed back to the pre-qwen36 single-path narrative. The long "qwen36 is unloadable / here are the workarounds" prose was replaced with a one-paragraph history note pointing at commit 964e418 + the CHANGELOG entry. Net -39 lines across the three doc surfaces.scripts/rename_arch.pyexample in Architecture flipped from qwen36 → qwen35 to qwen35 → qwen36 to match its new "tool kept around for future flips" framing. - Round trip (2026-05-19 evening). The bundle briefly flipped
back to
qwen36in commit07fa120plus the doc re-expansion ine9c7366, on the theory that architectural-honesty was worth the heal-hf friction. A fresh-pull integration test on the Strix Halo box reproduced the friction end-to-end (ollama run500 on first pull, recover viamake heal-hf, smoke-test then passed) — and the friction was worse in practice than the doc prose suggested. Reverted to qwen35 inhf uploadcommit72259c1+ the present commit (revert ofe9c7366). Net bundle state: same5ed60d0a...blob as before the round trip; no user-facing impact unless a user happened to pull in the ~1 hour window when the bundle was qwen36-stamped (for them, onemake heal-hfis the recovery).
Note
make heal-hf,make load-bundle, andscripts/rename_arch.pystay in the repo and stay useful. heal-hf is idempotent on qwen35 (skips early); load-bundle does a no-op rebadge on a qwen35 bundle and runsollama createas normal.
Changed (upstream-evidence audit — qwen35 is canonical, not a workaround)
- Read the actual upstream code paths for Qwen 3.6 loading and
found that
qwen35isn't a stand-in awaitingqwen36support; it's the upstream-canonical name for the whole Qwen 3.5 / 3.6 hybrid SSM + attention family. Three converging pieces of evidence, now folded into the README "Architecture" section:- Qwen's own HF configs for both
Qwen/Qwen3.6-27BandQwen/Qwen3.6-35B-A3Bdeclaremodel_type: "qwen3_5"/"qwen3_5_moe"and theQwen3_5*ForConditionalGenerationarch classes. NoQwen3_6arch class exists intransformers— Qwen reuses the 3.5 class names for 3.6 weights. convert_hf_to_gguf.pyinggml-org/llama.cppregistersQwen3_5ForCausalLM→MODEL_ARCH.QWEN35andQwen3_5MoeForCausalLM→MODEL_ARCH.QWEN35MOE, so the unsloth GGUFs this repo pulls from are natively qwen35-stamped out of the converter.src/models/qwen35.cpphas an explicitcase 64: type = LLM_TYPE_27Bbranch (this model);qwen35moe.cpphascase 40: type = LLM_TYPE_35B_A3B(the Janus-35B sibling base). The arch entries were written to load Qwen 3.6 weights, not just Qwen 3.5; this is code-level confirmation, not interpretation.
- Qwen's own HF configs for both
- Reframed the README "Architecture" section accordingly: the
three pieces of evidence are listed inline; the "qwen35 is the
canonical load-time label" claim is now grounded rather than
asserted; the History paragraph softens the v0.6.0 stamp choice
from "architecturally-honest" to "version-specific on the
theory that
qwen35was a loader stand-in awaitingqwen36support" — a theory the audit shows was mistaken. - Flipped the
scripts/rename_arch.pyexample back to qwen36 → qwen35 (the direction that actually serves the only realistic use case: legacy v0.6.0-era recovery). Previous direction (qwen35 → qwen36) made less sense given the audit conclusion. - Tooling unchanged.
scripts/rename_arch.py,make heal-hf,make load-bundlestill exist and still work; they're just documented honestly as legacy recovery + generic flip utility rather than as workarounds awaiting upstream changes that will never come.
Changed (round-trip residue cleanup)
- Five doc surfaces still carried the v0.6.0-era "bundle is
qwen36-stamped, fails today, rebadge to qwen35 first" narrative
that the earlier
83022eb/4811e8dcollapses missed. Reframed to describe the current qwen35-stamped reality:- README "What's here" intro paragraph (the secondary TL;DR-style block above Architecture): flipped the "qwen36-stamped → fails today, see Heads-up above" example to the same one-liner the primary TL;DR uses; dropped the "as a working-today fallback while the bundle waits on upstream qwen36 support" framing from the Q3_K_S paragraph since there's no longer anything to wait on.
- README "Local apps" intro paragraph: removed the entire "every row below assumes you've rebadged it to qwen35" caveat; trimmed "or to bypass the qwen36 block today" from the Ollama table cell.
- README "What's here" table cells for
load_bundle.shandheal_hf_pull.sh: reframed each as primary qwen35 path- legacy qwen36 recovery branch, instead of describing the rebadge as mandatory.
scripts/load_bundle.shpreamble: rewritten to describe the current qwen35-stamped bundle as the default; the qwen36 rebadge branch is documented as a fallback for legacy pre-964e418checkouts.scripts/heal_hf_pull.shpreamble: reframed as recovery for pre-964e418v0.6.0-era pulls; clarified that fresh pulls don't need it.- Makefile
load-bundlehelp text + example comment: dropped the "qwen36-stamped bundle, rebadged to qwen35" framing. Net -15 lines. Upstream re-poll (gh searchagainstggml-org/llama.cpp+ollama/ollamaforqwen36PRs/issues, 2026-05-19): no qwen36 arch entry, no open PR or tracking issue — same answer as the earlier subagent recheck; theqwen35stamp choice still stands. (Side-finding worth noting for a future README update: llama.cpp PR#22673merged 2026-05-16 adds MTP speculative decoding for Qwen3.6, so the Architecture section's "MTP head trained for speculative decoding" claim now has a usable consumer path — out of scope for this cleanup.)
Added
scripts/check.shnow greps for theVAR="$(cmd 2>/dev/null | filter)"silent-exit pattern that bitheal_hf_pull.sh(commit 385ed94). Pattern fires on any top-level assignment whose$(...)body both suppresses stderr (2>/dev/null) and pipes through a filter — the exact combination whereset -e + pipefailswallows the failure of the first command and aborts the script before any explicit[[ -z "${VAR}" ]]check can run, leaving the user staring atmake: *** [Makefile:N: <target>] Error 1with no diagnostic. Suggested fix in the failure message:if VAR=$(cmd 2>/dev/null); then VAR2=$(filter <<<"${VAR}"); fi. Currently passes (the heal-hf fix already uses the if-form); pre-commit hook +make checknow block regressions.
Fixed
scripts/heal_hf_pull.shsilent-exit when the target tag isn't pulled. The blob-locator line wasMODEL_BLOB="$(ollama show --modelfile "${TAG}" 2>/dev/null | awk ...)"; underset -e + pipefail, an unpulled tag makesollama showexit non-zero, pipefail propagates the non-zero through the pipeline, and set -e terminates the script before the explicit[[ -z "${MODEL_BLOB}" ]]check ever runs. The user saw onlymake: *** [Makefile:47: heal-hf] Error 1with no clue what was wrong. Refactored to use anif MODELFILE_OUT=...; thenform, which takes the false branch on failure without tripping set -e. The "tag not pulled?" hint now fires as intended.
Added
scripts/heal_hf_pull.shnow validates the rewritten manifest by runningollama show <tag>immediately after the atomic mv into place, with rollback-on-failure semantics. Before this, a buggy rewrite that produced jq-parseable but ollama-rejected JSON (e.g. layer order off, missing required mediaType, digest that no longer matches the on-disk blob bytes) would only be discovered at the user'sollama runstep — and by then the old qwen36 blob had already been removed, leaving the tag in a half-broken state with no easy recovery. Now: the scriptcps the original manifest to a.heal-backupsidecar before the rewrite, runsollama showagainst the rewritten manifest, and on failuremvs the backup back into place and exits non-zero. The old qwen36 blob cleanup (step 7, was step 6) only runs after validation passes, so a rollback always lands on a consistent state (original manifest + original blob still present). Backup is deleted on success.
[0.6.0] - 2026-05-19 — c336f44
Headline changes since 0.5.0 (over a two-week burst on 2026-05-19):
- Project renamed
Janus-27B→Thanatos-27B(HF repo, GGUF filenames, default Ollama tag, system-prompt identity, banner wordmark). MoE 35B siblingFoolDev/Janus-35Bkeeps the Janus name; the dense 27B splits off as its own identity. - Bundle GGUF re-stamped
general.architecture: 'qwen35'→'qwen36'— the architecturally-honest label. Breaksollama run hf.co/FoolDev/Thanatos-27Band directllama-server -m Thanatos-27B.Q4_K_M.ggufon every stock loader until upstream adds the arch entry (which, as of 2026-05-19, has no PR or tracking issue in either ggml-org/llama.cpp or ollama/ollama). make load-bundle— one-shot bundle → loadable local Ollama tag (LFS smudge + qwen36 → qwen35 rebadge +ollama create).make heal-hf— heal an already-pulled HF-bridge tag in store by rebadging its model blob qwen36 → qwen35 and rewriting the manifest's model-layer digest, so the samehf.co/...tag becomes loadable in place without switching tag names.scripts/rename_arch.py— generic GGUF arch renamer (metadata only, tensor data byte-identical). Used by load-bundle and heal-hf; reusable for future arch renames.- Tool-calling end-to-end via Ollama (Modelfile TEMPLATE wires
.Tools/.ToolCallssoollama showreports thetoolscapability and/api/chat//v1/chat/completionsaccept tools arrays). HF Ollama bridge gets parallel coverage via root-leveltemplate/system/paramsfiles (HF's bridge does not readModelfile); kept in sync viascripts/check_bridge_sync.py, wired intomake check. scripts/bench.sh+make bench— 3-prompt tok/s benchmark using Ollama'seval_count/eval_duration. Three measured data points logged on the Strix Halo reference hardware (Ryzen AI Max+ 395 / Radeon 8060S iGPU): Q3_K_S Vulkan 12.31 tok/s, Q4_K_M Vulkan 9.31 + 9.19 tok/s (two runs), Q3_K_S ROCm 10.14 tok/s (older backend snapshot).scripts/smoke_test.sh+make smoke/make smoke-tools— server reachable, model present, tools capability, round-trip, token-leakage guard, opt-in tool-call round-trip (TOOLS_TEST=1).- Vision section restructured around llama.cpp / llama-cpp-python
(Ollama vision is broken upstream —
qwen35/qwen35moearch entries present in Go engine, missing from the C++ fallback the Ollama runner switches to when mmproj is attached; ollama/ollama#15898).llama-server --mmprojHTTP path now the lead recommendation. - Bundled GGUF:
Thanatos-27B.Q4_K_M.gguf(~17 GB) is the only redistributed quant, picked by HF's Ollama bridge for the default:latesttag. Other quants (Q3_K_S, Q5_K_M, Q6_K, safetensors) pulled from upstream on demand viamake build QUANT=....
Bug-fix highlights:
scripts/fetch_mmproj.shrenamed toscripts/fetch_vision.sh— HF's Ollama bridge was filename-pattern-matchingmmproj*anywhere in the repo and shipping the 2-KB bash script as theapplication/vnd.ollama.image.projectorlayer, breakingollama show/ollama runwithError: invalid file magic.ModelfilePARAMETER stop directives for<|im_end|>,<|endoftext|>,<|im_start|>— Ollama was only picking up<|im_end|>from GGUF metadata, so the model kept generating past<|endoftext|>and synthesised a fake user turn.examples/ollama_chat.pythinking-trace handling under Ollama 0.24 (now readsmessage.thinking, falls back to<think>extraction for older builds).
See the full per-bullet changelog below.
Added
scripts/heal_hf_pull.sh+make heal-hf: heal an already-pulledhf.co/FoolDev/Thanatos-27B:...tag in-store by rebadging its model blob (qwen36 → qwen35, metadata-only, byte-identical tensors) and rewriting the manifest's model-layer digest. Covers the user pain whenollama run hf.co/FoolDev/Thanatos-27Bis typed from muscle memory, fails with the qwen36 500, and leaves ~17 GB of unloadable blob sitting in the store; before this, the only recovery wasollama rm <tag>+ switching to the separatethanatos-27btag thatmake load-bundlebuilds.make heal-hfmakes the samehf.co/...tag loadable in place. Idempotent (tags already on qwen35 / qwen35moe are skipped); content-addressed dedup means if the rebadged blob already exists in the store (e.g. from a priormake load-bundlerun) the heal reuses it instead of double-allocating ~17 GB. Removes the old qwen36 blob if no other manifest references it. Stages the rebadge in.cache/rather than/tmpso the ~17 GB write doesn't blow past tmpfs (mvintoblobs/stays an atomic same-filesystem rename on a normal Linux home-dir layout).- README TL;DR Ollama section now lists three paths: heal an
already-pulled HF tag (
make heal-hf), build from the bundle (make load-bundle), or bypass the bundle entirely (make build). Newscripts/heal_hf_pull.shentry added to the "What's here" table.
Added
- Second Q4_K_M Vulkan bench data point on the Strix Halo reference
hardware (same machine + config as the existing 9.31 tok/s entry):
9.19 tok/s aggregate (6210 tokens / 675.6 s; 9.40 / 9.29 / 9.16
short/medium/long). Measured 2026-05-19 against the qwen36-stamped
HF-bridge tag after
make heal-hfrebadged it to qwen35 in store — confirms the in-place heal produces a model with the same performance profile asmake load-bundle. Aggregate is 1.3% below the existing run-1 (9.31), well inside the ±20% noise band the README warns about. Modelfile hardware notes updated.
Changed
examples/README.mdOllama setup section: the previous "easiest path" told users toollama pull hf.co/FoolDev/Thanatos-27Bthen runollama_chat.pyagainst the pulled tag — which fails today because the bundle is qwen36-stamped (the pull succeeds; the first inference call returns the qwen36 500). Section rewritten to lead with the pull +make heal-hfrecovery (one extra command after pull, rebadges the blob in place so the samehf.co/...tag loads), and adds themake load-bundle/make build QUANT=...alternatives as the bypass-the-bundle paths that don't touch the HF pull at all. All three set MODEL explicitly so the right tag is used.- README "Architecture" section + Quick start option A:
- Architecture body now notes that neither
ggml-org/llama.cppnorollama/ollamahas an open PR or tracking issue for aqwen36arch string (rechecked 2026-05-19), and that the actual Qwen 3.6-family upstream weights already load underqwen35moe— so the bundle's qwen36 stamp may stay ahead-of-upstream indefinitely, not just for a few releases. "Once upstream adds qwen36..." softened to "If upstream ever adds qwen36...", and the list of things deletable on that day grew from justmake load-bundletomake load-bundle+make heal-hf+ the whole workaround. - "The one-liner" subsection became two one-liners — one for
the fresh-clone case (
make load-bundle), one for the already-pulled case (make heal-hf). Mirrors the TL;DR. - Quick start option A's comment now points at
make heal-hfas the in-place recovery for users who've already typed the directollama run hf.co/...form and hit the 500. Same block now lists both the failing one-liner and the heal + re-run recovery, so a skim-reader sees both without having to cross-reference the Architecture section.
- Architecture body now notes that neither
scripts/load_bundle.sh+make load-bundle: one-shot path from the qwen36-stamped bundle → loadable Ollama tag. Handles the LFS smudge (hf download FoolDev/Thanatos-27B Thanatos-27B.Q4_K_M.ggufif the local copy is a pointer), inspectsgeneral.architecture, runsscripts/rename_arch.py --from-arch qwen36 --to-arch qwen35if needed (idempotent — reuses an existing qwen35-rebadged file with correct arch, regenerates if mismatched), then patches theModelfileFROM and runsollama create thanatos-27b. Reduces the manual rebadge recipe in README "Architecture" from four copy-pasted commands to onemaketarget. Tag overridable viaTAG=…; bundle path viaBUNDLE=….- README Quick start now describes three Ollama paths (was
"two"): direct HF pull (fails today on qwen36),
make load-bundle(loads this repo's bundle today via the rebadge),make build(bypasses bundle, pulls qwen35 from unsloth). Heads-up callout near the top of the model card now points at the one-liner. Newscripts/load_bundle.shentry added to the "What's here" table. - Makefile
cleantarget now also removesThanatos-27B.*.qwen[0-9]*.ggufrebadges (already gitignored, butmake cleanshould sweep them too).
Fixed
scripts/smoke_test.shnow matches the model tag case-insensitively against/api/tags. Ollama 0.24 normalizes lookups at query time but preserves whatever case was first registered on disk, somake load-bundle(TAG=thanatos-27b) can produce a tag listed asThanatos-27B:latestif an earlier session left aThanatos-27Bmanifest dir behind. The case-sensitivestartswith($m)in the prior smoke check rejected the tag even thoughollama run thanatos-27bworked fine. Now usesascii_downcaseon both sides. Also added./scripts/load_bundle.shto the "build it first" hint shown on miss.- Stale doc claim that
make build"uses the bundled Thanatos-27B.Q4_K_M.gguf". It doesn't —scripts/build.shdefaults to${ROOT}/Qwen3.6-27B-${QUANT}.ggufand pulls fromunsloth/Qwen3.6-27B-GGUFif missing. README TL;DR + Quick start- Makefile comments updated. To actually load the in-repo bundle,
use the new
make load-bundle.
- Makefile comments updated. To actually load the in-repo bundle,
use the new
Changed
- BREAKING (loaders). Bundle re-stamped from
general.architecture: 'qwen35'to'qwen36'— the architecturally-honest label. No released llama.cpp / Ollama recognizesqwen36yet (reconfirmed 2026-05-19 against llama.cpp 389ff61 and Ollama 0.24.0), soollama run hf.co/FoolDev/Thanatos-27Band directllama-server -m Thanatos-27B.Q4_K_M.ggufboth fail withunknown model architecture: 'qwen36'until upstream adds the entry. To load today, rebadge to qwen35 locally withscripts/rename_arch.py --from-arch qwen36 --to-arch qwen35— see the README "Architecture" section for the one-liner. Tensor data stays byte-identical; only metadata flips. Reverses prior commitf0d70eewhich had restored qwen35 for compatibility; deliberate re-flip with eyes open about the breakage. - README "Heads up" callout near the top of the model card, TL;DR,
loader matrix, Quick start Ollama block, and Modelfile preamble
all updated to flag the qwen36 stamp and point users at the
rebadge workaround. Quick-start option B (
make buildfrom unsloth) called out as the loads-today path, since unsloth still ships qwen35-stamped GGUFs.
Added
- README "Vision via llama.cpp" subsection now leads with the
llama-server --mmprojHTTP path (always built into stock llama.cpp, no extra cmake targets needed), reconfirmed working 2026-05-19 with llama.cpp 389ff61 + Vulkan on the Strix Halo reference machine (bundled Q4_K_M GGUF +mmproj-F16.gguffromunsloth/Qwen3.6-27B-GGUF). Sent a 1024-px JPEG via an OpenAI-styleimage_urldata-URL content block; model produced an accurate description (Japanese alleyway with paper lanterns, bicycles, etc.) in 478 completion tokens. The visible answer arrived inmessage.content, the thinking trace inmessage.reasoning_content— the section notes both, plus the ≥500 max_tokens budget needed so the reasoning block doesn't crowd out the final answer. The existingllama-mtmd-cliandllama-cpp-pythonexamples are still listed;llama-mtmd-clinow carries a note that it's a separate cmake target — a plaincmake --build buildproduces it, but a selective build (e.g.cmake --build build --target llama-cli llama-server llama-bench) silently skips it, which is what tripped the retest on this box (LLAMA_BUILD_TOOLS=ONwas already set; the mtmd target just hadn't been requested). - Third measured tok/s data point on the Strix Halo reference hardware: Q3_K_S under Vulkan → 12.31 tok/s aggregate (6182 tokens / 501.9 s; 12.67 / 12.55 / 12.25 short/medium/long). Now apples-to-apples with the Q4_K_M Vulkan measurement (9.31 tok/s) — Q4_K_M is ~24% slower at this quant gap on this hardware. Also surfaces a backend delta: the older ROCm Q3_K_S snapshot was 10.14 tok/s, so Vulkan is ~21% faster than ROCm on the same hardware for this model. Modelfile hardware notes restructured to group by backend; README "Hardware requirements" reference paragraph updated to the Vulkan numbers with the ROCm comparison kept inline.
- README "Architecture" section: a reproducible recipe for testing the
qwen36arch path locally (scripts/rename_arch.py+ an inline Modelfile +ollama create+ a one-lineollama run). The intent is a fast way to check "is upstream support here yet?" — today it errors withunknown model architecture: 'qwen36', the day it succeeds is when the bundle gets flipped.
Fixed
- README qwen36-retest recipe had two bugs that surfaced on
reconfirmation against Ollama 0.24.0 on 2026-05-19:
echo "FROM ./Thanatos-27B.Q4_K_M.qwen36.gguf" > /tmp/Modelfile.qwen36never resolves:ollama createlooks up a relativeFROMagainst the Modelfile's directory (/tmp), not the user's CWD. The CLI fails with400 Bad Request: invalid model namebefore the loader runs, which is misleading. Recipe now usesFROM $PWD/Thanatos-27B.Q4_K_M.qwen36.gguf.- The CLI error users see today is no longer
unknown model architecture: 'qwen36'— Ollama 0.24's scheduler wraps the loader and surfaces only500 Internal Server Error: unable to load model: <blob path>. The arch-unknown line still appears in the server log (journalctl --user -u ollama). README now spells out where to look for the real cause and stamps the reconfirmation date.
- Canonicalized HF repo URL case throughout docs and scripts:
FoolDev/thanatos-27b→FoolDev/Thanatos-27B(the actual HF repository name). Touched README.md, examples/README.md, examples/ollama_chat.py, CITATION.cff, scripts/smoke_test.sh, scripts/check_bridge_sync.py, and the earlier CHANGELOG entry that described the rename plan as if it targeted the lowercase form. Local Ollama tagthanatos-27b(set in the Makefile / build.sh / smoke_test.sh / bench.sh defaults) stays lowercase — only theFoolDev/<repo>URL was capitalized. HF's 307 redirect resolves the lowercase form transparently, but linking to the canonical case avoids relying on that redirect and matches the HF web UI. examples/README.md"Why not Ollama?" section: sameqwen35/qwen35moestatus split applied earlier to the Modelfile / README /llama_cpp_vision.py— text via the Go engine works on 0.24+, vision via the C++ fallback path still blocked by ollama/ollama#15898.examples/llama_cpp_vision.py"Why this script exists" preamble: replaced the overbroad "Ollama 0.22's vendored llama.cpp fork is missing the qwen35/qwen35moe arch entries" claim with the same split the README and Modelfile now use — Go engine has the entries (text works on 0.24+), C++ fallback used when mmproj is attached still lacks them. Issue link unchanged (#15898 still open). Keeps the script's "use llama.cpp directly for vision" recommendation intact, just stops misleading users about which Ollama codepath is broken.examples/ollama_chat.pysimple-chat demo no longer silently swallows the thinking trace on Ollama 0.24. The demo parsed<think>...</think>blocks out ofmessage.content— a workaround dating to Ollama 0.22 when reasoning was inlined intocontent. On the current build (especially withOLLAMA_NEW_ENGINE=1) Ollama returns thinking in a dedicatedmessage.thinkingfield and keepscontentclean, so the regex extracted nothing and the demo printed[answer]only. Now readsmessage.thinkingfirst and falls back to<think>extraction for older builds; docstring updated to match.
Added
- Second measured tok/s data point on the Strix Halo reference
hardware (ASUS ROG Flow Z13 GZ302EA / Ryzen AI Max+ 395 + Radeon
8060S iGPU, 32 GB unified, gfx1151): Q4_K_M, num_ctx 16384,
3-prompt mix → 9.31 tok/s aggregate (5356 tokens / 574.9 s;
9.48 / 9.43 / 9.28 short/medium/long), measured under the systemd
unit's tuned config (
OLLAMA_FLASH_ATTENTION=1,OLLAMA_KV_CACHE_TYPE=q8_0,OLLAMA_VULKAN=1). About 8% slower than the existing Q3_K_S entry (10.14 tok/s) on the same hardware, consistent with the larger weights (17 GB vs 12 GB). Modelfile hardware notes + README "Hardware requirements" reference block updated. The earlier change-set's "verified locally … at ~10 tok/s" CHANGELOG note was an estimate; this is the real number.
Fixed
- Reverted accidental
general.architecturerebadge of the bundled GGUF (qwen35→qwen36). Commit 2dbe526 stamped the Q4_K_M GGUF witharch='qwen36'(via the newscripts/rename_arch.py); no released ollama or upstream llama.cpp recognizes that arch string, soollama run hf.co/FoolDev/Thanatos-27Bfailed at load withunknown model architecture: 'qwen36'. Reverted in f0d70ee — LFS pointer back to the originalqwen35blob, same Q4_K_M tensor data as upstreamunsloth/Qwen3.6-27B-GGUF. The mirroring rebadge on the siblingFoolDev/Janus-35B(qwen35moe→qwen36moe) was also reverted in that repo's commit ee40cb2.
Added
scripts/rename_arch.py— generic GGUF arch renamer. Walks the KV section, renamesgeneral.architectureand every<old>.*namespaced key, and rewrites tensor data byte-for-byte (no quant change). Used once for theqwen35→qwen36experiment above (since reverted); kept inscripts/as a reusable tool for future arch renames if needed. Picked up byscripts/check.shvia the existing pyflakes / py_compile passes..gitignorepatternThanatos-27B.*.qwen[0-9]*.ggufto exclude local rebadge outputs fromrename_arch.pyso they don't accidentally land in LFS and ship via the HF Ollama bridge. The existing!Thanatos-27B.*.ggufallow-rule was permissive enough to catch the rebadged copy when adding files in bulk.
Changed
- README "Loader compatibility" table +
## Known limitationsbullet- Modelfile header comment: split the Ollama
qwen35/qwen35moestatus into two halves. Text inference works in current ollama (the Go engine has the arch entries — verified locally with the bundled Q4_K_M GGUF at ~10 tok/s on Strix Halo); only the C++ llama.cpp fallback — which Ollama switches to when an mmproj is attached — still lacks them, so vision (mmproj) remains broken upstream while text is fine. Earlier wording implied the entire vendored fork lacked the arch entries, which was overbroad and would mislead users into expecting text to fail too. Bumped the table's "Ollama 0.22" version reference to 0.24. Upstream tracking issue ollama/ollama#15898 is still open.
- Modelfile header comment: split the Ollama
Renamed
- Project renamed
Janus-27B→Thanatos-27B. The 35B-A3B MoE sibling (FoolDev/janus) keeps the Janus name; this repo splits off as its own identity. In-repo rewrite covered:README.md,Modelfile,params/system/template,Makefile,CITATION.cff,.gitignore,examples/**,scripts/**. The bundled GGUF was renamedJanus-27B.Q4_K_M.gguf→Thanatos-27B.Q4_K_M.gguf(git mv, LFS pointer preserved). Default Ollama tag flippedjanus-27b→thanatos-27b. System prompt identity flipped "You are Janus" → "You are Thanatos" everywhere it appears (Modelfile,system,examples/*.py). Sibling references back toFoolDev/janusandJanus-35B-A3Bare preserved on purpose — those still point at the MoE. - HF-side rename done: the HF repository was renamed
FoolDev/janus-27b→FoolDev/Thanatos-27Bin HF Settings → "Rename or transfer this repository". One-time URL break is unavoidable: existingollama pull hf.co/FoolDev/janus-27bcallers now 404. (Case is canonicalThanatos-27B; HF's 307 redirect resolvesthanatos-27bto it transparently, but in-repo docs use the canonical form throughout.)
Changed
banner.svgwordmarkJANUS-27B→THANATOS-27B. Font-size dropped 26 → 22 to keep the longer 12-char wordmark inside the same 383×77 viewBox without encroaching on the activation-grid dots in the upper right. Tokyo Night palette unchanged:#c0caf5mark,#bb9af7highlight on the suffix, gradient backdrop, animated activation grid- token-stream beam preserved.
banner.pngregenerated from the updatedbanner.svgviarsvg-convertat the same 383×77 dimensions.dense-flow.svgleft unchanged — it has no wordmark, only the 64-layer hybrid-attention pulse visualization.
Added
- README top-row badge linking to
buymeacoffee.com/Thanatos-27B. Tokyo Night yellow (e0af68on1a1b26labelColor) so it sits alongside the existing License/Base/Arch/Sibling badges without visual fight. Tip-jar only — no functional change to the model or tooling.
Removed
Janus-27B.Q3_K_S.ggufno longer redistributed in this repo. Removing it leavesJanus-27B.Q4_K_M.ggufas the only GGUF, which flips HF's Ollama bridge default-tag pick from Q3_K_S to Q4_K_M unambiguously:ollama run hf.co/FoolDev/janus-27bnow resolves to the 17 GB Q4_K_M blob instead of the 12 GB Q3_K_S blob. This is the resolution to the long-running "Q4 to widget" thread — rename hacks broke HF's GGUF detection (see earlier "Investigated (and reverted)" entry), and HF doesn't expose a documented default-quant metadata field, so the only reliable fix was to remove the conflicting file. Q3_K_S users build it locally withmake build QUANT=Q3_K_S, which downloadsQwen3.6-27B-Q3_K_S.gguffromunsloth/Qwen3.6-27B-GGUFand patches the Modelfile FROM line into a temp copy. Tradeoff: 12 GB less data shipped through the repo, smaller-footprint users do an extra ~3 minute download, default Ollama tag now matches the README's recommendation. Updated Modelfile / README / examples / CITATION.cff to reflect Q4_K_M as the only bundled quant.
Investigated (and reverted)
- Tried renaming
Janus-27B.Q3_K_S.gguftoJanus-27B.q3_k_s.gguf(lowercase quant suffix) hoping to flip HF's default-:latest-tag pick from Q3_K_S to Q4_K_M. The theory was that HF picks the alphabetically-first GGUF and lowercase 'q' (0x71) sorts after uppercase 'Q' (0x51), so Q4_K_M would become first. Result: HF stopped recognizing the repo as GGUF entirely. After commit 6973bea (since reverted) the HF API returnedgguf.total: nullandgguf.totalFileSize: null, andollama pull hf.co/FoolDev/janus-27bfailed with400 Repository is not GGUF or is not compatible with llama.cpp. HF's GGUF-detection regex evidently requires the uppercase-quant pattern, so lowercasing it broke the integration entirely — worse than the original Q3_K_S-default behavior. Reverted toJanus-27B.Q3_K_S.gguf. The "Pin the explicit:Q4_K_Mtag" documentation in the README / examples / docstring stays as the practical fix; we don't have control over HF's default-pick logic.
Added
scripts/smoke_test.shopt-in tool-call round-trip (gated byTOOLS_TEST=1). The previous smoke test only verified thetoolscapability is advertised (template references.Tools/.ToolCalls); it did not verify the model actually emits a parseable tool call. A regression in the prompt scaffolding inside theTEMPLATEblock can leave capabilities reported correctly but tool emission failing — the assistant prose-describes the call instead of emitting a<tool_call>{...}</tool_call>block. The new section sends a tools-array request, parses.message.tool_calls, and asserts the shape (name=get_weather,city=Tokyocase-insensitive). Off by default to keepmake smokefast (1 sec); on for10 sec). Verified end-to-end againstmake smoke-tools(hf.co/FoolDev/janus-27b:Q3_K_S.make smoke-toolstarget wrapsTOOLS_TEST=1 ./scripts/smoke_test.sh.
Added
scripts/check_bridge_sync.py— regression guard against drift between theModelfileand the root-level HF Ollama bridge files (template/system/params). The two configurations are consumed by different code paths —Modelfilebyollama create -f Modelfilefor local builds, the three bridge files by HF's Ollama bridge when usersollama run hf.co/...directly (HF does not readModelfile). If they drift apart, the two pull paths produce different behaviour, exactly the bug fixed in commit 33458f7. The script parses the Modelfile'sTEMPLATE/SYSTEM/PARAMETERdirectives, loads the three bridge files, and fails loudly on any mismatch with a per-key diff. Wired intoscripts/check.shso the existing pre-commit hook runs it. Verified end-to-end by deliberately mutatingparamsand confirming the check exits 1 with a clear diagnostic.
Changed
- README +
examples/README.md+examples/ollama_chat.pydocstring: recommend the explicit:Q4_K_Mtag (and:Q3_K_Sas the smaller-footprint alternative) instead of the bareollama run hf.co/FoolDev/janus-27bform. HF's Ollama bridge picks the alphabetically-first GGUF as the default (:latest), and our filenames sort asJanus-27B.Q3_K_S.gguf<Janus-27B.Q4_K_M.gguf, so the bare form pulls the 12 GB Q3_K_S blob, not the 17 GB Q4_K_M the docs previously implied is "default". Verified empirically:ollama pull hf.co/FoolDev/janus-27bresolves to model SHA4afb4abcf020(12 GB), and the HF API exposesgguf.totalFileSize: 12358727904for the repo. HF's published Ollama docs say Q4_K_M should be the default when present, but in practice the alphabetical-first heuristic wins; until that changes, pinning the tag explicitly is the only reliable way to get the recommended quant.
Fixed
- README
## TL;DRblock previously claimed thehf.co/...snippet "pulls a GGUF + this repo's Modelfile, including the tool-calling TEMPLATE". HF's Ollama bridge does NOT readModelfile— it ingests the root-leveltemplate/system/paramsfiles (per https://huggingface.co/docs/hub/en/ollama). Updated the TL;DR to state that explicitly so users skimming the top of the model card don't form the wrong mental model of which file is in play. Also fixed themake buildcomment ("downloads Q4_K_M (default)") to match the current behaviour:make builduses the bundledJanus-27B.Q4_K_M.gguffrom the repo without downloading.
Added
- Root-level
template,system, andparamsfiles for HF's Ollama bridge. The bridge generates Ollama manifests at request time from these three files (NOT fromModelfile— confirmed against https://huggingface.co/docs/hub/en/ollama). Without them,ollama run hf.co/FoolDev/janus-27bgot an auto-generated manifest with the broken{{ if .Prompt }} .Prompt }}<|im_end|>template (Ollama's faulty Go-template conversion of the GGUF's embedded jinja), corrupted stop tokens (".Prompt }}<|im_end|>"bleed), and no.Tools/.ToolCallsblocks — so the published Ollama tag advertisedcompletiononly, rejected any request with atoolsarray, and was actually broken to load (see "Fixed" below re: the projector layer). The three files mirror theModelfile'sTEMPLATE/SYSTEM/PARAMETERdirectives; both routes wire tool calling correctly. Edit them together when changing one.
Fixed
- Renamed
scripts/fetch_mmproj.sh→scripts/fetch_vision.sh. HF's Ollama bridge was filename-pattern-matchingmmproj*anywhere in the repo and shippingscripts/fetch_mmproj.sh(a 2028-byte bash script) as theapplication/vnd.ollama.image.projectorlayer. When Ollama tried to load that "projector" as a GGUF, it failed the magic-bytes check andollama show/ollama runproducedError: invalid file magic. Renaming the script breaks the pattern match and the projector layer is no longer added to the manifest. UpdatedMakefile(mmprojtarget) and README references to point at the new name.
Changed
- README "Tool / function calling" section: split into explicit
Ollama-path and embedded-jinja-path subsections. The two loader
paths produce different on-the-wire formats and the previous text
conflated them. The Ollama path (this repo's
ModelfileTEMPLATE) prompts JSON-in-XML — the form Ollama's tool-call extractor parses into a structuredtool_callsarray. The embedded-jinja path (llama.cpp, llama-cpp-python, LM Studio) reads the Qwen 3.6 native chat template baked into the GGUF, which prompts the model to emit the verbose<function=name>/<parameter=arg>form it was trained on. Both are valid; the model adapts to whichever shape its system prompt prescribes. README now shows both formats with a concrete example each so users pick the parser that matches their loader. - README "Chat template" intro: clarified that Ollama is the lone
loader that needs the
ModelfileTEMPLATEoverride; everything else gets correct plain-conversation formatting from the embedded jinja directly. Previous wording implied all loaders handled it automatically.
Fixed
examples/ollama_chat.pydocstring: step 2 ("Edit ../Modelfile so the FROM line points at the GGUF path") was stale — commit 3d2e907 pointed the bundledModelfileat./Janus-27B.Q4_K_M.gguf, which the repo now ships, so the no-edit flowollama create janus-27b -f ../Modelfileworks out of the box. Replaced the four-step list with two paths (bundled-GGUF or pull-from-HF) covering both flows.examples/README.md: header said "Three minimal entry points" but the table lists four (ollama_chat.py,transformers_quickstart.py,llama_cpp_quickstart.py,llama_cpp_vision.py).examples/README.md"Or build locally with a different upstream quant" snippet was stale: it told users tohf downloadQ4_K_M (now bundled) and manually edit../ModelfileFROM(the bundled file already points at./Janus-27B.Q4_K_M.gguf). Replaced with the canonicalmake buildflow, plus a note thatmake build QUANT=...handles a non-bundled quant by patchingFROMin a temp Modelfile copy automatically (matches whatscripts/build.shactually does).CITATION.cffabstract: claimed "weights are pulled from upstream … rather than redistributed" but commits b0d8482 (Q4_K_M) and 4c20ab5 (Q3_K_S) added bundled GGUFs to the repo. Updated to acknowledge the two redistributed quants while noting other quants + safetensors are still pulled on demand.
Added
Modelfilehardware notes: log a measured data point for the ASUS ROG Flow Z13 GZ302EA (Ryzen AI Max+ 395 / Radeon 8060S iGPU, 32 GB unified, ROCm gfx1151) — Q3_K_S at num_ctx 16384 reads 10.14 tok/s aggregate over the bench.sh 3-prompt mix (10.37 / 10.31 / 10.11 short/medium/long, 8080 tokens / 796.5 s). Backs up the README's ~10 tok/s reference number with the exact measurement that produced it. The previous⚠ 32 GB unified-memory laptops — borderlinerow stays because it covers the Q4_K_M case.
Fixed
Makefile: propagateTAGtoscripts/build.sh. The build target declaresTAG ?= janus-27bat the top of the file (and lists it in the help block) but didn't actually pass it through, somake build TAG=janus-27b-q3 QUANT=Q3_K_Sproducedjanus-27b:latestinstead ofjanus-27b-q3:latest. Combined with the matchingscripts/build.shchange in the previous commit, the override now works end-to-end via either entry point.scripts/build.sh: acceptTAGas an env override (was hardcoded tojanus-27b, silently ignoring callers that wanted a different Ollama tag — e.g.TAG=janus-27b-q3 ./scripts/build.sh Q3_K_Sproducedjanus-27b:latestinstead ofjanus-27b-q3:latest). Default behaviour unchanged whenTAGis unset. Also fixes cosmetic section numbering in the comments (skipped from# ---- 2.to# ---- 4.with no section 3).Modelfile: change FROM from./Qwen3.6-27B-Q4_K_M.ggufto./Janus-27B.Q4_K_M.gguf. The repo now ships GGUFs (commit b0d8482 added Q4_K_M, commit 4c20ab5 added Q3_K_S), so the no-script pathgit clone && ollama create janus-27b -f Modelfileworks out-of-the-box for users with LFS smudge enabled. Comment block refreshed: removed the stale "does not redistribute weights" line (contradicted by the bundled GGUFs) and added a pointer tomake build QUANT=Q3_K_Sfor the smaller quant.scripts/build.shstill patches FROM in a temp Modelfile copy, so existing build flows are unchanged.
Added
examples/ollama_chat.py: respectMODELandHOSTenv vars (matches the convention scripts/smoke_test.sh and scripts/bench.sh already use). Lets you run the demo against the HF-pulled model withMODEL=hf.co/FoolDev/janus-27b python ollama_chat.pyinstead of editing the constant.ollamatag in the model-card frontmatter so HF surfaces the repo in Ollama-compatible collections / filters alongside the existinggguftag.- README "Local apps" subsection enumerating how Ollama, LM Studio, Jan, llama.cpp, llama-cpp-python, Open WebUI/KoboldCpp/TGW pick this model up from the bundled GGUFs. HF's "Use this model" widget auto-shows most of these once it parses the GGUFs, but the README stands on its own and notes which apps currently honor the embedded tool-calling template (Ollama via the Modelfile TEMPLATE, llama.cpp / llama-cpp-python via the GGUF's jinja).
Janus-27B.Q4_K_M.gguf(17 GB) and12 GB) shipped in the repo so HF's "Use this model" widget surfaces the Ollama snippet (Janus-27B.Q3_K_S.gguf(ollama run hf.co/FoolDev/janus-27b) and Ollama can pull weights + Modelfile in one step. Q4_K_M is the default tag;:Q3_K_Sis the smaller-footprint option for 16 GB GPUs / unified-memory laptops. Addsggufto the model-card tags so HF lists the repo among GGUF models. Loosens.gitignorefrom*.ggufto allowJanus-27B.*.ggufwhile still excluding the upstreamQwen3.6-27B-*.ggufthatmake buildfetches locally. README's "does not redistribute weights" wording reworked: the wrapper philosophy still holds for the upstream Qwen GGUFs, but we ship two tagged distributions to back the HF/Ollama UX.
Fixed
Modelfile: ship aTEMPLATEdirective mirroring Qwen 3.6 ChatML in Ollama Go-template form, so Ollama's tool-capability detector sees.Tools/.ToolCallsreferences. Aftermake build,ollama show janus-27bnow liststoolsandthinkingunder Capabilities, and both/api/chatand/v1/chat/completionsaccept atoolsarray (previously rejected withdoes not support tools). Same template as the 35B sibling — both share the Qwen 3.6 chat format. Verified end-to-end withexamples/ollama_chat.py:tool_round_trip(model emits a<tool_call>, helper executes the stub, model produces final answer). README "Tool / function calling" rewritten and the corresponding Known-limitations bullet removed.Modelfile: added explicitPARAMETER stopdirectives for<|im_end|>,<|endoftext|>, and<|im_start|>. Ollama was only picking up<|im_end|>from the GGUF metadata, so when the model emitted<|endoftext|>it kept generating past it and synthesised a fake new user turn. Reproducible viamake smokebefore/after.
Added
scripts/bench.sh+make bench: tok/s benchmark using Ollama'seval_count/eval_durationresponse metadata (avoids client-side stopwatch noise). Mixes short / medium / long prompts so the average doesn't lock onto a single output shape, and warms up first so the model-load cost doesn't pollute the first sample. Reference data: Ryzen AI Max+ 395 / Radeon 8060S iGPU clocks ~10 tok/s at Q3_K_S. README hardware section now points readers atmake benchto measure their own rather than trusting the estimated table.scripts/smoke_test.sh: token-leakage guard. The previous round-trip check passed on any non-empty response — including the broken EOS-bleed output that motivated the stop-token fix above. The smoke test now fails hard if<|im_start|>,<|im_end|>, or<|endoftext|>appear verbatim in the response.scripts/smoke_test.sh: tools-capability guard. Hits/api/showand assertstoolsis in the capabilities list before running the round-trip. Locks in the ModelfileTEMPLATEwork — if a future change strips or breaks the TEMPLATE so Ollama no longer detects.Tools/.ToolCalls,make smokefails before the user discovers it via adoes not support tools400.
Removed
Modelfile.z13and thez13build profile. The Z13-specific tuning (Q3_K_S + 8K ctx + FA + q8_0 KV cache) wasn't meaningfully different from "use a smaller quant on a 32 GB unified-memory laptop", so the separate Modelfile and thePROFILEplumbing inMakefile/scripts/build.shwere removing value rather than adding it. README now points 32 GB users atmake build QUANT=Q3_K_Sdirectly.
Changed
- README "Why a 27B variant?": the "slower per token than 35B-A3B"
claim now ships with a measured comparison — 27B Q3_K_S clocks ~10
tok/s vs ~27 tok/s for the 35B at ~Q4 on the same Ryzen AI Max+ 395
/ Radeon 8060S iGPU (
make bench). Same hardware, asymmetric quants (35B doesn't ship a Q3_K_S), but the ~2.7x ratio is what you'd expect from ~3B vs 27B active params. - README +
examples/README.mdVision sections: tightened the Ollama-0.22 failure-mode description. Previously implied thatollama createitself errors withunknown model architecture. Empirically (against denseqwen3527B +mmproj-F16),ollama createsucceeds,ollama showeven reports thevisioncapability with a CLIP projector attached, and the architecture error only fires from the runner on the first inference request — at which point it blocks text inference too. Matches the upstream issue's "blocks ALL inference" phrasing. - README: added a TL;DR section right after the intro paragraph so someone scanning the page gets a working command without scrolling past Why / What's here / Architecture.
- README chat-template section: replaced the cross-reference to the
35B sibling card with inlined examples (plain conversation,
<think>reasoning trace, XML tool call). The README now stands on its own. - Minor: dropped the "Same as the 35B sibling:" lead-in above the system prompt block; the prompt was already inlined.
[0.5.0] - 2026-05-02 — e4beea4
Added
examples/llama_cpp_vision.py— image-text-to-text viallama-cpp-python+ a separatemmproj-F16.gguf. Currently the only working vision path for Janus-27B (Ollama is broken; see Changed).scripts/fetch_mmproj.sh— pullsmmproj-F16.gguf(or BF16/F32) fromunsloth/Qwen3.6-27B-GGUF. HonorsMMPROJ_PATHoverride.Makefile: newmmprojtarget.
Changed
- README: replaced the misleading "Multimodal: Yes (vision)" comparison
row with a per-loader breakdown. Added a dedicated Vision section
documenting:
- The Ollama 0.22 architecture gap (
unknown model architecture: 'qwen35moe') tracked in ollama/ollama#15898. - A working llama.cpp / llama-cpp-python path with the
mmproj. - Updated Known Limitations entry accordingly.
- The Ollama 0.22 architecture gap (
ModelfileandModelfile.z13: header comments now state explicitly that they're text-only and link to the Vision section.examples/README.md: reflects the new Vision example and explains why Ollama is not the recommended backend for it (yet).
[0.4.0] - 2026-05-02 — 9ca8700
Added
Makefile— convenience wrapper.make helplists targets:build/smoke/check/hooks/clean. VariablesQUANT,PROFILE,TAG,GGUF_PATH,MODELare overridable.
Changed
scripts/build.shnow honorsGGUF_PATHso users with weights already on disk (e.g. shared model dirs, NAS mounts) don't have to download or symlink. Also defers the HF-CLI requirement until a download is actually needed..gitignorenow excludes.cache/,*.incomplete, and*.lockso ephemeral artifacts from runninghf downloadinside the repo never get committed.
[0.3.0] - 2026-05-02 — 7766f0b
Added
scripts/check.sh— local lint runner:bash -n, optionalshellcheck,pyflakes,py_compile, plus a guard regex for the dash/dot filename bug. Returns non-zero on failure.scripts/install-hooks.sh— installscheck.shas a git pre-commit hook so footguns can't slip past again.CHANGELOG.md.
[0.2.1] - 2026-05-02 — 82677d0
Fixed
- Wrong GGUF filename pattern in
Modelfile,Modelfile.z13,scripts/build.sh,examples/README.md, andexamples/llama_cpp_quickstart.py. Upstreamunsloth/Qwen3.6-27B-GGUFuses dashes (Qwen3.6-27B-Q4_K_M.gguf), not dots — without this fixscripts/build.sh404s on first run.
Changed
- README architecture section corrected. Qwen 3.6 27B is not pure
GQA; it's a hybrid stack of
[3 × Gated DeltaNet → 1 × Gated Attention]repeated 16 times, per the upstream Qwen 3.6 docs. Added MTP head note and YaRN ~1 M context ceiling. - README comparison table: Q4_K_M is ~17 GB (was 16); added Q3_K_S row; qualified the min-host-memory entry by quant + context size.
scripts/build.shnow lists known-good quant suffixes inline so typos likeQ4_KMare easier to catch.
[0.2.0] - 2026-05-02 — 6f2884f
Added
LICENSE(Apache-2.0) andCITATION.cff.Modelfile.z13— Q3_K_S, 8K ctx, FA + q8_0 KV cache profile that fits in the Ryzen AI Max+ unified memory pool (~17 GB total).examples/:ollama_chat.py— chat, streaming, tool round-trip, OpenAI-compattransformers_quickstart.py— bnb 4-bit by default,--no-4bitfor bf16llama_cpp_quickstart.py— daemon-free GGUF inferenceexamples/README.md— picks between the three
scripts/build.sh— one-shot GGUF pull +ollama createfor default and z13 profiles.scripts/smoke_test.sh— server-reachable + model-present + round-trip..gitignore.
Changed
- README updated to reference the new files; replaced the "borderline"
Z13 caveat with a working profile (
Modelfile.z13).
[0.1.0] - 2026-05-02 — b564869
Initial public release of the Janus-27B model card.
Added
- README.md, banner.svg / banner.png (Tokyo Night themed),
Modelfile,.gitattributes.