SAVRN
Search Contact SAVRN

Open-weight model · Text generation

Thanatos-27B-HERETIC

by Fool FoolDev/Thanatos-27B-HERETIC

Architecture: Qwen 3.8 27B (Dense) | Parameters: 27B | Context: 262,144 native / 1,010,000 extensible | Base: Heretic ARA (heretic-org) | Teacher: Claude Fable 5 | Type: Distilled + Abliterated LLM A personal sibling to FoolDev/Janus-35B-HERETIC, which since…

Parameters
Context
Weights16.5 GB
Licenseapache-2.0
AccessOpen weights
Monthly Downloads916

Model Card

By Fool, published under apache-2.0, revision 8f009ecb63be.

Architecture: Qwen 3.8 27B (Dense) | Parameters: 27B | Context: 262,144 native / 1,010,000 extensible | Base: Heretic ARA (heretic-org) | Teacher: Claude Fable 5 | Type: Distilled + Abliterated LLM A personal sibling to FoolDev/Janus-35B-HERETIC, which since its 0.9.0 is a Qwen 3.6 35B-A3B MoE — so the two are genuinely different models, not two abliterations of one base (see Janus vs Thanatos). This one is the dense Qwen/Qwen3.8-27B, sharing the teacher (Claude Fable 5) and dataset family, built on heretic-org/Qwen3.8-27B-heretic-ara, an uncensored Heretic ARA-method abliteration, with refusal-trained behavior dialed back at the base layer. (The sibling's -35B- slug names its model…

Read Fool's full model card

Thanatos-27B

Dense Reasoning. Friendlier Footprint. Uncensored. heretic-org's Heretic (ARA) abliteration of Qwen 3.8 27B (dense), repackaged with Claude Fable 5 in the teacher slot.

Architecture: Qwen 3.8 27B (Dense) | Parameters: 27B | Context: 262,144 native / 1,010,000 extensible | Base: Heretic ARA (heretic-org) | Teacher: Claude Fable 5 | Type: Distilled + Abliterated LLM

A personal sibling to FoolDev/Janus-35B-HERETIC, which since its 0.9.0 is a Qwen 3.6 35B-A3B MoE — so the two are genuinely different models, not two abliterations of one base (see Janus vs Thanatos). This one is the dense Qwen/Qwen3.8-27B, sharing the teacher (Claude Fable 5) and dataset family, built on heretic-org/Qwen3.8-27B-heretic-ara, an uncensored Heretic ARA-method abliteration, with refusal-trained behavior dialed back at the base layer. (The sibling's -35B- slug names its model correctly again.)

Provenance. This repo repackages and quantizes — no training is performed here. What ships is heretic-org/Qwen3.8-27B-heretic-ara converted to GGUF and MTP-stripped so stock llama.cpp and Ollama can load it. The Teacher: field above and the datasets: list in the frontmatter are carried over from earlier revisions of this card. The current base does not claim them — its card lists no datasets and names no teacher — and nothing in this repo reproduces or measures them. One listed dataset, Crownelius/Opus-4.7-Reasoning, is not publicly reachable: the Hub answers 401, as it does for a private or missing repo. Treat them as unverified attribution, not as a training record. base_model_relation is quantized for the same reason.

TL;DR

One-liner via Hugging Face (pulls a GGUF + this repo's root-level template / system / params files, including the tool-calling template — HF's Ollama bridge ingests those three files, not Modelfile):

ollama run hf.co/FoolDev/Thanatos-27B-HERETIC           # ~17 GB Q4_K_M, qwen35-stamped, loads on stock Ollama

Bundled blob status: the bundled Thanatos-27B.Q4_K_M.gguf is the heretic-ara Q4_K_M quant (from mradermacher/Qwen3.8-27B-heretic-ara-GGUF), qwen35-stamped and verified against the Architecture claims below (make verify-arch). Pulling hf.co/FoolDev/Thanatos-27B-HERETIC serves the uncensored heretic-ara behavior directly — no local build required. make build remains the path for other quants.

If you pulled the bundle during any of the qwen36 windows on the pre-rename FoolDev/Thanatos-27B repo (2026-05-19/20) and still have a qwen36-stamped blob in your local Ollama store, pull the current repo: ollama pull hf.co/FoolDev/Thanatos-27B-HERETIC. That blob is the May 2026 build on vanilla Qwen/Qwen3.6-27B, from before both base swaps. Fresh pulls go straight through.

For other quants (Q3_K_M ~13 GB, Q5_K_M ~20 GB, etc.), make build QUANT=... is the simplest path. See Quick start below for the full matrix. The heretic-ara GGUF repo publishes quants from Q2_K (~10.9 GB) upward, including IQ4_XS.

For image input use llama.cpp directly — Ollama vision is broken for this architecture upstream (see Vision).

Heads-up — context defaults. The baked default is the full 262144 native window: a 16 GiB KV cache and ~33 GiB total with Ollama's default f16 cache, or 8.5 GiB / ~25 GiB with OLLAMA_KV_CACHE_TYPE=q8_0. It's extensible to 1,010,000 with YaRN. On a 32 GB host use the q8_0 cache (tight) or lower num_ctx before the first message — 65536 is ~20 GiB total with the f16 cache. See Hardware requirements.

To halve the KV cache, start the Ollama server with the q8_0 cache and flash attention (for a systemd install, add them as Environment= lines with sudo systemctl edit ollama):

bash OLLAMA_KV_CACHE_TYPE=q8_0 OLLAMA_FLASH_ATTENTION=1 ollama serve

Janus vs Thanatos

They are different models again. Janus moved back to the Qwen 3.6 35B-A3B MoE in its 0.9.0 (2026-09-18), after a day of base changes in both directions; this repo has been the dense Qwen 3.8 27B throughout and did not move. So the choice between them is now about shape as well as abliteration:

Thanatos-27B (this) Janus-35B
Architecture Dense Qwen 3.8 27B — every parameter on every token Qwen 3.6 35B-A3B MoE — ~34.7B total, ~3B active
Layers 64 (16 full-attention / 48 linear) 40 (10 full-attention / 30 linear)
Experts none 256, 8 routed per token + 1 shared
Abliteration method Heretic ARA Heretic MPOA-style
Immediate base heretic-org/Qwen3.8-27B-heretic-ara llmfan46/Qwen3.6-35B-A3B-uncensored-heretic
MTP head in the source weights Present — stripped in the bundle Declared by the base, absent from the bundled quant — no strip needed
Q4_K_M GGUF size ~16.5 GB (bundled) 21.23 GB (bundled)
Weights resident ~15.4 GiB ~19.78 GiB — every expert stays resident
KV cache at f16 2 GiB per 32K 0.625 GiB per 32K — under a third, from 10 KV layers instead of 16
Qwen generation 3.8 3.6
Repo slug Thanatos-27B-HERETIC Janus-35B-HERETIC — accurate again

Pick this one for the newer Qwen generation, a smaller resident footprint and a dense compute profile. Pick Janus for 35B-class capacity at ~3B active per token and a much cheaper KV cache at long context. Janus's figures above are derived from its GGUF's geometry rather than measured; this card's are measured.

Janus changed base three times on 2026-09-18 — MoE in 0.7.0, dense Qwen 3.8 27B in 0.8.0, back to the MoE in 0.9.0 — so its CHANGELOG reads as a round trip. If you pulled Janus that day, ollama pull again to match its card. Thanatos did not move.

Everything else is shared: dense 27B all-active, 64 layers, hidden 5120, FFN 17408, 24 Q / 4 KV heads at head_dim 256, vocab 248,320, qwen35 GGUF arch stamp, 262,144 native context (1,010,000 extensible with YaRN), the same text and vision capabilities, and the same upstream Ollama vision breakage.

What's here

File Use
banner.svg / banner.png Repo header, Tokyo Night themed
dense-flow.svg / dense-flow.png Architecture diagram: 64-layer hybrid attention stack with animated forward-pass pulse (SVG); static frame fallback (PNG)
bmc-banner.svg Animated "Buy me a coffee" support banner, Tokyo Night themed (rising steam, hot-cup glow, sweeping beam, blinking cursor)
Modelfile Ollama wrapper around the bundled GGUF (the heretic-ara Q4_K_M, qwen35-stamped) — used by make build / ollama create for local builds
template, system, params Used by HF's Ollama bridge when users ollama run hf.co/FoolDev/Thanatos-27B-HERETIC directly (the bridge does not read Modelfile — see HF Ollama docs). Mirrors the Modelfile's template / system prompt / sampling params.
chat_template.jinja Chat template for llama.cpp / llama-server, already stamped into the bundled GGUF (pass --chat-template-file only for an unstamped quant). The base model's own embedded template with three changes: a tool_call_format setting — xml by default, upstream's form and the one llama-server's parser reads, with auto as an opt-in for loaders that pass JSON-string arguments through; reasoning_effort normalised instead of validated; and an assistant turn's reasoning read from reasoning, reasoning_content or thinking, where upstream reads only reasoning_content. Earlier turns' reasoning is replayed by default, as upstream's is (see Thinking is replayed across turns). Also stamped into the GGUF by scripts/build.sh. See Tool / function calling.
examples/ Ready-to-run Python clients for Ollama, Transformers, and llama-cpp-python
scripts/build.sh Pulls a qwen35-stamped GGUF from mradermacher/Qwen3.8-27B-heretic-ara-GGUF and runs ollama create (loads on today's llama.cpp / Ollama; see make build). The bundled Q4_K_M is already this heretic-ara quant; use this to build other quants locally.
scripts/load_bundle.sh One-shot path from this repo's bundle → loadable local Ollama tag (downloads the real blob into .cache/ if the working-tree file is still an LFS pointer — it does not smudge in place — then runs ollama create; see make load-bundle). Carries a qwen36 → qwen35 rebadge branch for legacy pre-rename checkouts — no-op on the current qwen35-stamped bundle.
scripts/cap_ctx.sh Bakes a small-num_ctx local tag (bundled blob + this repo's Modelfile, num_ctx→4096, num_batch 256) for OpenAI /v1 clients — which can't override the baked num_ctx and OOM on small hosts (see Inference). Run make cap-ctx (or CTX=8192 ./scripts/cap_ctx.sh).
scripts/smoke_test.sh Verifies an Ollama daemon + model, runs a round-trip, asserts no chat-template tokens leak into the response. With TOOLS_TEST=1, also exercises an end-to-end tool-call round-trip and checks the response shape
scripts/bench.sh Measures real tok/s using Ollama's eval_count / eval_duration metadata over a 3-prompt mix (run make bench)
scripts/fetch_vision.sh Pulls the vision projector (Qwen3.8-27B-heretic-ara.mmproj-f16.gguf from the Heretic GGUF repo, or mmproj-F16.gguf from the unsloth reference projector) for llama.cpp (Ollama vision is broken upstream — see Vision). Renamed from fetch_mmproj.sh because HF's Ollama bridge auto-indexed the script as a vision projector layer (filename pattern match).
scripts/check.sh Local lint: bash -n, pyflakes, py_compile, footgun-grep, Modelfile-vs-bridge-files sync, the chat_template.jinja render checks and the Go template guard (make check)
scripts/check_chat_template.py Renders chat_template.jinja with jinja2 and asserts the tool-call, thinking, reasoning-replay (on by default, each opt-out, a conflicting kwarg, a live tool chain's kept) and reasoning_effort behaviour (check 8 in check.sh)
scripts/check_go_template.py Guards the Go template: Ollama's thinking detection, the thinking condition that replays every earlier turn's reasoning, the tool round trip, JSON tool signatures, and the reasoning-effort mapping — the xhigh and low arms and the medium/unset default on this path (check 9 in check.sh)
scripts/live_check.sh Live end-to-end checks in an isolated, CPU-only Ollama (own port and model store; it refuses to run if Ollama reports a GPU): template selection, one tool call, string-argument replay, an earlier turn's reasoning replayed and a live tool chain's kept, every reasoning_effort value and the effort lines — 17 checks (make live-check)
scripts/check_bridge_sync.py Verifies the Modelfile TEMPLATE / SYSTEM / PARAMETER directives stay in sync with the root-level template / system / params files. Run as part of make check; called from the pre-commit hook.
scripts/verify_arch.py Cross-checks the README "Architecture" forward-pass bullets (layer count, head counts, hidden / FFN dims, RoPE factor, SSM dims, vocab, context) against the actual GGUF metadata keys. Run as make verify-arch. Handles both qwen35- and qwen36-stamped bundles; exit non-zero if any value mismatches. Not part of make check because it loads the 17 GB GGUF (LFS smudge required); run on demand.
scripts/strip_mtp.py Drops the MTP / NextN layer from a qwen35 GGUF (python3 scripts/strip_mtp.py IN.gguf OUT.gguf): removes the extra blk.<last>.* block, sets block_count back to 64, and drops nextn_predict_layers. Kept tensors are copied byte-for-byte (no re-quant); a conditional no-op (hardlink) on already-clean quants. Preps the bundled blob and is run automatically by build.sh on every fetched quant, since the upstream heretic-ara GGUFs ship the MTP layer that stock llama.cpp / Ollama can't load — see Architecture.
scripts/install-hooks.sh Installs check.sh as a git pre-commit hook
Makefile Convenience wrapper — make help lists targets
LICENSE, CITATION.cff Apache-2.0 license and citation metadata
CHANGELOG.md Versioned tooling/docs changes
README.md This file

For 16 GB GPUs / unified-memory laptops, make build QUANT=Q3_K_M downloads the smaller ~13 GB Q3_K_M quant from mradermacher/Qwen3.8-27B-heretic-ara-GGUF (qwen35-stamped, loads directly) and creates a local thanatos-27b Ollama tag. Does not redistribute via this repo. For other quants use make build QUANT=.... The local-build path applies this repo's Modelfile; the hf.co/... path applies the root-level template, system, and params files (kept in sync with the Modelfile).

If you want the Heretic safetensors for transformers, fetch them from heretic-org/Qwen3.8-27B-heretic-ara. For the vanilla pre-Heretic Qwen 3.8 27B base, use Qwen/Qwen3.8-27B.

Architecture

  • Qwen 3.8 dense, 27B parameters, 64 transformer layers
  • Hybrid attention stack: 16 repeats of [3 × (Gated DeltaNet → FFN) → 1 × (Gated Attention → FFN)]
  • Gated DeltaNet (linear attention): 48 V-heads, 16 QK-heads, head_dim 128
  • Gated Attention (softmax): 24 Q-heads, 4 KV-heads (GQA), head_dim 256, partial RoPE (factor 0.25)
  • Hidden size 5120, FFN intermediate 17408 (~3.4× ratio)
  • Vocab 248,320 (the Janus sibling's MoE shares this vocab size, on a different base)
  • 262 144 native context (extensible to 1 010 000 with YaRN, but YaRN is not enabled in the bundled GGUF)
  • Vision + video supported by the base architecture via a separate mmproj projector (not redistributed here; pull Qwen3.8-27B-heretic-ara.mmproj-f16.gguf from mradermacher/Qwen3.8-27B-heretic-ara-GGUF, or mmproj-F16.gguf from unsloth/Qwen3.8-27B-GGUF as a reference alternative). See Vision below for current loader compatibility.
  • Multi-token prediction (MTP) head trained for speculative decoding — present in the upstream Qwen/Qwen3.8-27B safetensors (mtp_num_hidden_layers: 1) and usable via vLLM (qwen3_next_mtp) or SGLang (--speculative-algo NEXTN). Current qwen35 GGUF conversions (e.g. mradermacher/Qwen3.8-27B-heretic-ara-GGUF) keep the MTP tensors as an extra NextN block, so those files report block_count 65 (= 64 transformer layers + 1 MTP layer). But today's stock llama.cpp / Ollama loaders can't yet load an MTP-included qwen35 GGUF — they treat the NextN block as a normal hybrid layer and abort with missing tensor 'blk.64.ssm_conv1d.weight' (verified on llama.cpp + Ollama 0.30.8). So the bundled Thanatos-27B.Q4_K_M.gguf is MTP-stripped (block 64 removed, block_count 64) so ollama run and llama.cpp load it directly. For the MTP head itself, pull cygnal/Qwen3.8-27B-heretic-ara-Q4_K_M-MTP-GGUF and run it on a NextN-capable llama.cpp build, or use the upstream safetensors via vLLM/SGLang.

The bundled GGUF declares general.architecture: 'qwen35' — not a workaround for an unimplemented qwen36 arch, but the canonical upstream label for the entire Qwen 3.5 / 3.6 / 3.8 hybrid SSM + attention family. The naming convergence runs through three layers of the stack:

  • Qwen's own HF configs. Qwen/Qwen3.8-27B/config.json declares "model_type": "qwen3_5" and "architectures": ["Qwen3_5ForConditionalGeneration"]. The MoE sibling Qwen/Qwen3.6-35B-A3B declares "qwen3_5_moe" / Qwen3_5MoeForConditionalGeneration. No Qwen3_6 arch class exists in transformers; Qwen reuses the 3.5 class names.
  • llama.cpp's converter. convert_hf_to_gguf.py registers Qwen3_5ForCausalLMMODEL_ARCH.QWEN35 and Qwen3_5MoeForCausalLMMODEL_ARCH.QWEN35MOE. The Heretic GGUFs this repo pulls from (mradermacher/Qwen3.8-27B-heretic-ara-GGUF) inherit those stamps, as do the upstream unsloth GGUFs (unsloth/Qwen3.8-27B-GGUF, unsloth/Qwen3.6-35B-A3B-GGUF).
  • llama.cpp's model code. src/models/qwen35.cpp has an explicit case 64: type = LLM_TYPE_27B branch for this model; qwen35moe.cpp has case 40: type = LLM_TYPE_35B_A3B for the 35B-A3B MoE base Janus shipped before its own 27B rebase. The arch entries were written to load Qwen 3.6 / 3.8 weights, not just Qwen 3.5.

There is no PR or tracking issue for a qwen36 arch entry in ggml-org/llama.cpp or ollama/ollama because none is needed — qwen35 already loads the model the upstream code path was designed to load.

ollama run hf.co/FoolDev/Thanatos-27B-HERETIC and llama-server -m Thanatos-27B.Q4_K_M.gguf both load directly on current stock loaders.

History

The bundle's general.architecture stamp has now flipped eight times — four landings on qwen36 and four on qwen35 — each time after weighing the friction-vs-honesty tradeoff anew. The saga is resolved on the upstream-canonical qwen35 side:

  • v0.6.0-era (e1f78fa, 2026-05-19 14:38 UTC): initial qwen35 → qwen36 stamp, on the theory that qwen35 was a loader stand-in awaiting proper Qwen 3.6 support. Upstream audit later showed that theory was mistaken (see above).
  • 2026-05-19 afternoon (964e418): flipped back to qwen35 after daily friction outweighed version-specificity for that iteration; doc workaround narrative collapsed (83022eb).
  • 2026-05-19 evening (07fa120): brief re-flip to qwen36 during a fresh-pull integration test on Strix Halo.
  • 2026-05-19 evening (72259c1, ~1 hour later): reverted to qwen35 again because the live friction was worse than the doc prose suggested.
  • 2026-05-19 evening (973d7ef): flipped to qwen36 one more time, after the upstream-evidence audit had been shipped and the friction was a known quantity. Project owner wanted to test the friction tradeoff in practice with the audit's conclusion staring them in the face.
  • 2026-05-19 evening (978798f): flipped back to qwen35 after seven sequential fresh-pull → heal-hf cycles on the Strix Halo box made the friction concretely-experienced rather than hypothetical. Each cycle worked (the heal flow is solid) — and each cycle was an unnecessary obstacle for users who just want ollama run to work first try. The audit (a4d3b6e) called the canonical stamp correctly and the practical friction outweighed the version-specificity payoff.
  • 2026-05-20 midday (ae67ed1): brief re-flip to qwen36 the next morning to re-test the friction in a fresh session.
  • 2026-05-20 midday (e03e10e, 8 minutes later): flipped back to qwen35. Same conclusion as the prior round trip — friction outweighs version-specificity. This is the current state.

Tensor data was byte-identical across all stamps; only the general.architecture KV (and namespaced KV keys) flipped. See the CHANGELOG entries for each flip's rationale.

Rebadge utility

scripts/rename_arch.py is the generic GGUF arch renamer (metadata only, tensors byte-identical), kept in the repo for the legacy qwen36 → qwen35 rebadge of a local bundle (used by make load-bundle) and any future arch flip:

# qwen36 -> qwen35 (the legacy recovery direction, for blobs
# pulled from the pre-rename FoolDev/Thanatos-27B repo)
python3 scripts/rename_arch.py \
    --from-arch qwen36 --to-arch qwen35 \
    Thanatos-27B.Q4_K_M.qwen36.gguf \
    Thanatos-27B.Q4_K_M.gguf

Quick start

Ollama

Three paths:

# A. Pull straight from HF (gets the bundled Q4_K_M GGUF + the
#    root-level template / system / params files in one step):
ollama run hf.co/FoolDev/Thanatos-27B-HERETIC           # 17 GB Q4_K_M, qwen35-stamped

# B. Build a local `thanatos-27b` tag from THIS repo's bundle
#    (LFS smudge if needed, then `ollama create`). Useful if you
#    want a bare local tag rather than the `hf.co/...` path:
make load-bundle                                 # creates local tag thanatos-27b
ollama run thanatos-27b

# C. Bypass the bundle: download a qwen35-stamped heretic-ara GGUF
#    and build locally. Loads on every current llama.cpp / Ollama.
make build                                              # Q4_K_M  -> thanatos-27b
make build QUANT=Q3_K_M                                 # 13 GB smaller quant
make build QUANT=Q5_K_M                                 # 20 GB higher quality
make build GGUF_PATH=~/models/Qwen3.8-27B-heretic-ara.Q4_K_M.gguf   # skip download
ollama run thanatos-27b

Under the hood, make build calls scripts/build.sh, which downloads the GGUF if missing (set GGUF_PATH to point at one you already have) and runs ollama create with the matching Modelfile.

If you'd rather do it by hand: edit the FROM line in Modelfile and run ollama create thanatos-27b -f Modelfile && ollama run thanatos-27b.

Confirm everything works:

make smoke                          # checks server, model, round-trip, no token leakage
make smoke-tools                    # adds an end-to-end tool-call round-trip (~10s extra)
make bench                          # measured tok/s on this machine (3-prompt mix)
python examples/ollama_chat.py      # full demo: chat, streaming, tools, OpenAI-compat

Local apps

App How to load this model
Ollama ollama run hf.co/FoolDev/Thanatos-27B-HERETIC (default Q4_K_M). Pulls the GGUF + the root-level template / system / params files in one step (HF's Ollama bridge ingests these three files; it does not read Modelfile). For other quants, make build QUANT=Q3_K_M downloads from mradermacher/Qwen3.8-27B-heretic-ara-GGUF and creates a local Ollama tag using the Modelfile, which is kept in sync with the bridge files.
LM Studio Search → FoolDev/Thanatos-27B-HERETIC → pick Thanatos-27B.Q4_K_M.gguf (current bundled filename — the heretic-ara Q4_K_M). Uses the GGUF's embedded jinja chat template (Qwen 3.8 ChatML); set the system prompt manually from the SYSTEM block in this repo's Modelfile.
Jan Hub → "Import from Hugging Face" → FoolDev/Thanatos-27B-HERETIC. Same template behavior as LM Studio.
llama.cpp hf download FoolDev/Thanatos-27B-HERETIC Thanatos-27B.Q4_K_M.gguf --local-dir . then llama-server -m Thanatos-27B.Q4_K_M.gguf (or llama-cli, llama-mtmd-cli for vision via Qwen3.8-27B-heretic-ara.mmproj-f16.gguf from the Heretic GGUF repo). Tool calling needs only --jinja — the bundled GGUF already carries this repo's fixed chat template. Add --chat-template-file chat_template.jinja only for a quant pulled straight from upstream, whose template raises on common reasoning_effort values such as high.
llama-cpp-python See examples/llama_cpp_quickstart.py (text) and examples/llama_cpp_vision.py (image input).
Open WebUI / KoboldCpp / text-generation-webui Standard llama.cpp loader path — point at the GGUF, use the embedded chat template.

For the full Vision (image input) loader matrix, see Vision. Tool calling works in Ollama (via the root-level template file when pulling from hf.co/..., or via the Modelfile TEMPLATE when building locally) and in llama.cpp / llama-cpp-python — but on llama.cpp with --jinja. The bundled GGUF carries this repo's fixed chat template, so no extra flag is needed; a quant pulled straight from upstream does need --chat-template-file chat_template.jinja, because its embedded template raises on common reasoning_effort values such as high, which llama-server returns as HTTP 500. Other apps' tool-calling support depends on whether they read the embedded template or require an external schema.

Inference (OpenAI-compatible)

The examples use model: "thanatos-27b", the tag from the local build (make build / ollama create thanatos-27b). If you pulled via the TL;DR one-liner instead, use the full tag hf.co/FoolDev/Thanatos-27B-HERETIC, or run ollama cp hf.co/FoolDev/Thanatos-27B-HERETIC thanatos-27b once to create the short tag.

On memory-tight hosts, cap num_ctx first. /v1/chat/completions (OpenAI-compat) has no num_ctx knob, so it loads at the baked 262,144 default (16 GiB KV / ~33 GiB total with Ollama's default f16 cache), which does not fit a 32 GB box; OLLAMA_KV_CACHE_TYPE=q8_0 on the server brings it to ~25 GiB (see Hardware requirements). Either call /api/chat with "options": {"num_ctx": 4096}, or bake a small-context tag for OpenAI clients: ollama run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M, then /set parameter num_ctx 4096 and /save thanatos-27b, and point clients at thanatos-27b — or in one step, make cap-ctx (which bakes exactly that tag).

curl -s http://localhost:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "thanatos-27b",
    "messages": [
      {"role": "system", "content": "You are Thanatos, a precise reasoning assistant."},
      {"role": "user", "content": "Explain the Burrows-Wheeler transform in 200 words."}
    ],
    "temperature": 0.6
  }' | jq -r '.choices[0].message.content'

Recommended sampling

Use temp top_p top_k repeat_penalty
Default (Fable-matched) 1.0 0.95 0 1.05
Tighter reasoning 0.6 0.95 20 1.05
Creative / RP 0.8 0.95 40 1.02

The shipped default is Fable-matched — warm (temperature 1.0), no top_k, with top_p 0.95 + repeat_penalty 1.05 kept as loop insurance. Drop to the reasoning row for tighter, more deterministic output; lower temperature (0.4-0.6) and bump repeat_penalty to 1.08 if it loops inside <think> tags.

top_p does not survive the OpenAI-compatible endpoint. Ollama's /v1/chat/completions layer applies OpenAI's own default of top_p 1.0, overwriting the 0.95 baked into params / the Modelfile. Measured on this model: the same request logs top_p = 0.950 on /api/chat and top_p = 1.000 on /v1/chat/completions. Since the default profile also sets top_k 0 (disabled), a /v1 request samples with no truncation at all at temperature 1.0 — the "loop insurance" above is only in effect on the native API. If you need that guard on /v1, send top_p explicitly on every request, or bake min_p (e.g. PARAMETER min_p 0.05) into a local tag — min_p and top_k both pass through /v1 untouched, top_p does not.

System prompt

The Modelfile bakes this in. Override per-request via the system role in your client:

You are Thanatos, a precise and capable assistant for reasoning, writing, coding, and long-form dialogue.

Behavior rules:
- Answer the user's actual request directly.
- Be accurate, complete, and structured.
- Think before answering, but do not get stuck in repetitive loops or meta-commentary.
- If the request is ambiguous or incomplete, state what is missing and make the smallest reasonable assumption needed to continue.
- If the user wants creative writing, preserve tone, continuity, and character consistency.
- If the user wants analysis or technical help, prefer concrete steps, examples, and decisions over fluff.
- Finish with a usable answer, not just planning.

Vision

The Qwen 3.8 base supports image (and video) input via a separate mmproj projector. The full multimodal stack is:

Thanatos-27B.Q4_K_M.gguf                 (~16.5 GB, the text decoder — this repo's bundle)
Qwen3.8-27B-heretic-ara.mmproj-f16.gguf  (~928 MB, the vision projector)

Use the bundled decoder, not the raw upstream quant. The heretic-ara GGUFs at mradermacher ship the MTP / NextN layer that stock llama.cpp cannot load (see Architecture), so -m Qwen3.8-27B-heretic-ara.Q4_K_M.gguf aborts at load. This repo's Thanatos-27B.Q4_K_M.gguf is the same quant already MTP-stripped — it is what the commands below use, and it saves a second ~17 GB download. If you do want an upstream quant, run it through scripts/strip_mtp.py first (make build does this automatically).

The projector is at mradermacher/Qwen3.8-27B-heretic-ara-GGUF; ./scripts/fetch_vision.sh pulls it into the repo root. For the vanilla pre-Heretic projector, see unsloth/Qwen3.8-27B-GGUF (mmproj-F16.gguf, ~927 MB). This repo intentionally does not redistribute either.

Loader compatibility — the honest table

Loader Text Vision (mmproj) Notes
llama.cpp (llama-mtmd-cli, llama-server --mmproj) Yes Yes Reference path. Upstream has the qwen35/qwen35moe arch entries.
llama-cpp-python Yes Yes See examples/llama_cpp_vision.py.
Ollama (0.33.3, 0.34.0 and 0.34.2 checked in source) Yes No Text inference works: Ollama's Go engine has the qwen35 / qwen35moe arch entries. The version floor this row used to claim ("0.24+") was undated and unsourced; these three are the checked set. Vision (mmproj) is still broken: the C++ llama.cpp fallback that Ollama switches to when an mmproj is attached lacks those entries. ollama create accepts a dual-FROM (text + mmproj) and ollama show reports vision capability — but the first inference request fails with error loading model architecture: unknown model architecture: 'qwen35' (or 'qwen35moe'), and once mmproj is attached this blocks text inference too. See ollama/ollama#14575 (closed as completed on 2026-08-04; #15898 was closed as its duplicate and the sync PR #15899 closed unmerged). Re-test on a current Ollama before assuming vision is still broken — this table has not been re-verified since it closed.
LM Studio Yes (last tested) Uses upstream llama.cpp directly.

Vision via llama.cpp

Three flavors, in order of build-time effort:

# A. HTTP via llama-server (always built — the easiest path).
#    Reconfirmed working 2026-05-19 against llama.cpp 389ff61 + Vulkan
#    on a Ryzen AI Max+ 395 / Radeon 8060S iGPU.
llama-server \
  -m Thanatos-27B.Q4_K_M.gguf \
  --mmproj Qwen3.8-27B-heretic-ara.mmproj-f16.gguf \
  --host 127.0.0.1 --port 8765 -c 8192 -ngl 99
# then POST OpenAI-style chat completions with an image_url content
# block — e.g. {"type":"image_url","image_url":{"url":"data:image/jpeg;base64,..."}}
# The thinking trace arrives in message.reasoning_content; the visible
# answer is in message.content. Budget ≥500 max_tokens so the reasoning
# block doesn't crowd out the final answer.

# B. CLI via llama-mtmd-cli (one-shot). It's a separate cmake target,
#    so a selective `cmake --build build --target llama-cli ...` won't
#    produce it — a plain `cmake --build build` will. If yours didn't,
#    run `cmake --build build --target llama-mtmd-cli`.
llama-mtmd-cli \
  -m Thanatos-27B.Q4_K_M.gguf \
  --mmproj Qwen3.8-27B-heretic-ara.mmproj-f16.gguf \
  --image photo.jpg \
  -p "Describe this image."

# C. Python via llama-cpp-python:
python examples/llama_cpp_vision.py \
  --gguf Thanatos-27B.Q4_K_M.gguf \
  --mmproj Qwen3.8-27B-heretic-ara.mmproj-f16.gguf \
  --image /path/to/photo.jpg \
  --prompt "What is in this image?"

The Ollama issue is closed upstream but this table has not been re-verified since; until you have re-tested, treat Ollama as text-only for this model.

Hardware requirements

Janus is a 35B-A3B MoE since its 0.9.0, so its figures no longer transfer: 10 full-attention layers instead of 16 give it a far smaller KV cache (0.625 GiB per 32K f16), while its weights are larger at ~19.78 GiB.

This is a ~16.5 GB Q4_K_M GGUF. Measured on this repo's own blob on 2026-09-18, Ollama 0.33.3's CPU backend, isolated store, CPU only, from llama.cpp's own allocation lines at num_ctx 8192 / 32768 / 65536 and again under OLLAMA_KV_CACHE_TYPE=q8_0 with OLLAMA_FLASH_ATTENTION=1. (Earlier releases carried these figures measured on the Janus sibling while it shared this architecture; they are now this model's own.)

component measured scales with num_ctx?
Weights 15.40 GiB (CPU model buffer 4935.19 MiB + CPU_REPACK 10835.16 MiB) no
Recurrent state (48 linear-attention layers) 149.62 MiB no — identical at every context
KV cache, f16 (Ollama's default) 65,536 B/token → 512 / 2048 / 4096 MiB at 8K / 32K / 64K, i.e. 2.0 GiB per 32K yes, exactly linear
KV cache, q8_0 34,816 B/token → 1088 / 2176 MiB at 32K / 64K, i.e. 1.0625 GiB per 32K yes, exactly linear
Compute buffer 280.04 / 328.04 / 784.07 MiB at 8K / 32K / 64K (q8_0: 328.30 / 784.34) yes, but not linearly

llama.cpp logs the cache as "16 layers", confirming only the full-attention layers are cached — the Janus sibling's MoE logs 10, which is why its KV is under a third of this one's at the same window.

num_ctx f16 cache (default) q8_0 cache
262144 (default) 16.0 GiB KV, ~32.3 GiB total 8.5 GiB KV, ~24.8 GiB total
65536 4.0 GiB KV, 20.3 GiB total (measured) 2.13 GiB KV, 18.4 GiB total (measured)
32768 2.0 GiB KV, 17.9 GiB total (measured) 1.06 GiB KV, 16.9 GiB total (measured)
1010000 (needs YaRN) 61.6 GiB KV, ~77.9 GiB total 32.7 GiB KV, ~49.0 GiB total

The 32768 and 65536 rows are sums of measured parts. The 262144 and 1010000 rows extrapolate the KV cache — exact, since it is linear — and carry the compute buffer forward from 65536, the largest context measured; it grows faster than linearly between 32K and 64K, so treat those two rows as floors. It's extensible to 1,010,000, but this GGUF ships no YaRN rope-scaling, so positions past the 262144 native window use untrained RoPE and output degrades — reaching the 1.01M ceiling needs YaRN (see below); keep real work within ~262K otherwise.

How to override it: ollama run has no -o flag, and OLLAMA_CONTEXT_LENGTH only sets a default the baked num_ctx overrides — so set it per-session from the interactive prompt (the model loads lazily on the first message, so /set applies before the default context is allocated):

ollama run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M
>>> /set parameter num_ctx 4096
>>> /set parameter num_batch 256

Programmatic callers pass the same via the API options field: "options": {"num_ctx": 4096, "num_batch": 256}.

Hardware Status
≥48 GB RAM (CPU-only) Works at the 262144 default (~32.3 GiB); 5.08 tok/s measured on a Ryzen AI Max+ 395. A 32 GB host needs OLLAMA_KV_CACHE_TYPE=q8_0 (~24.8 GiB, tight) or num_ctx 65536 (20.3 GiB measured)
RTX 3090 / 4090 24 GB Works with partial offload — the ~32.3 GiB total at the 262144 default does not fit 24 GB; trim num_ctx for full offload
RTX 5090 32 GB Works, full offload at higher quant (Q5/Q6) with a lower num_ctx than the 262144 default, ~30-50 tok/s
Mac Studio M2/M3 48 GB+ unified Works at the default, ~15-25 tok/s (32 GB needs a lower num_ctx)
32 GB unified-memory laptops (Mac M-series, Ryzen AI Max+, etc.) Does not fit the 262144 default with the f16 cache; the q8_0 cache (~24.8 GiB) is tight, num_ctx 65536 (18.4-20.3 GiB measured) is comfortable. make build QUANT=Q3_K_M (~13 GB) or a lower num_ctx for comfort.

Most numbers in this table are estimates from comparable models; the gradient is right but the absolute values will move ±20% with prompt shape, KV cache type, and parallel-request count. Measure your own machine with make bench (3-prompt mix, reports tok/s from Ollama's eval_count / eval_duration so it's not stopwatch-noisy). On the current blob, on a Ryzen AI Max+ 395 with Ollama 0.33.3's CPU backend (no GPU), make bench measured 5.08 tok/s at Q4_K_M — aggregate over its three-prompt mix after a discarded warmup, 1,852 generated tokens in 364,560 ms, or 5.32 / 5.10 / 5.07 individually, so the rate is steady across short / medium / long prompts (re-measured 2026-09-18 in an isolated store; a 0.12.0-era run of the same bench gave 5.01). The q8_0 KV cache costs nothing in speed here either — 5.22 and 5.12 tok/s on the first two prompts of the same mix against 5.32 and 5.10 at f16; that pass was stopped before its third prompt finished, so no q8_0 aggregate is published. The earlier data points on that machine's Radeon 8060S iGPU under Vulkan — ~12.3 tok/s at Q3_K_S and ~9.3 tok/s at Q4_K_M, with a ROCm snapshot of the Q3_K_S bench at ~10.1 tok/s — were measured in May 2026 on the Qwen 3.6 27B builds, before both base swaps, and have not been re-measured on the heretic-ara blob.

GPU offload on a Strix Halo iGPU (Radeon 8060S): a small gain, and a trap. On a Vulkan integrated GPU, llama.cpp - including the build Ollama 0.33.3 bundles - turns mmap off and puts every layer it does not offload into GPU-reachable host memory from the same pool as the GPU, so num_gpu alone does not limit GPU memory: a first attempt with llama-server filled the pool and took the desktop down with it. With LLAMA_ARG_NO_HOST=1 in the Ollama server's environment (llama.cpp's --no-host) those layers stay in ordinary RAM. Measured that way on the Janus sibling when it was the same dense Qwen 3.8 27B architecture, 17.3 against 16.5 GB (a private Ollama 0.33.3 with the ollama-vulkan backend and OLLAMA_IGPU_ENABLE=1; flash attention, q8_0 KV cache, num_ctx 4096, a short generation): 4.8 tok/s at num_gpu 4, 4.9 at 8, 5.3 at 16 and 5.7 at 24 (7.9 GiB of the GPU pool), against about 5.0 on CPU only; prompt processing roughly doubled (61 against 30 tok/s). The step to 32 layers ran the 27 GiB host short of RAM and was stopped: the GPU's GTT is system RAM, so offloading frees none. On a machine like this, CPU-only is nearly as fast; if you try the GPU, set LLAMA_ARG_NO_HOST=1 and a fixed num_gpu. The mechanism is reported upstream in ggml-org/llama.cpp#27360.

Reaching a coherent ~1.01M context (opt-in YaRN). The bundled GGUF ships no YaRN rope-scaling, so raising num_ctx toward the 1.01M ceiling degrades past the 262144 native window. Ollama has no rope knob, so for a genuinely coherent long context run the GGUF under llama.cpp with YaRN enabled:

llama-server -m Thanatos-27B.Q4_K_M.gguf \
  --rope-scaling yarn --yarn-orig-ctx 262144 --rope-scale 3.853 -c 1010000

--rope-scale 3.853 ≈ 1010000 / 262144; use a smaller factor for a smaller window. Static YaRN rescales all prompts, so enable it only when you actually need > 262K — it slightly degrades short-context quality otherwise.

Chat template

Standard Qwen 3.x ChatML with <|im_start|> / <|im_end|> role markers and <think>...</think> blocks for reasoning traces. The Qwen 3.8 jinja template is embedded in the GGUF metadata; loaders that read GGUF chat templates directly (llama.cpp, llama-cpp-python, LM Studio) handle the plain-conversation formatting automatically.

Ollama is the exception: it picks between this repo's Go template and the embedded jinja at load time (see Which template Ollama runs). Two paths supply that Go template, depending on how you pull the model:

  • ollama run hf.co/FoolDev/Thanatos-27B-HERETIC — HF's Ollama bridge applies the root-level template / system / params files in this repo (the bridge does not read Modelfile).
  • make build / ollama create thanatos-27b -f Modelfile — uses the Modelfile's TEMPLATE block.

Both routes wire .Tools / .ToolCalls and tools work end-to-end on /api/chat and /v1/chat/completions. The two configurations are kept in sync: edit them together if you change one.

Plain conversation

<|im_start|>system
You are Thanatos, a precise and capable assistant…<|im_end|>
<|im_start|>user
What is the time complexity of mergesort?<|im_end|>
<|im_start|>assistant

With reasoning trace

<|im_start|>assistant
<think>
The user asked about mergesort. It splits, recursively sorts each half,
then merges. The recurrence T(n) = 2T(n/2) + O(n) solves to O(n log n).
</think>

Mergesort runs in **O(n log n)** time in the worst, average, and best
cases.<|im_end|>

Most clients (Open WebUI, LibreChat, etc.) hide the <think> block by default and surface only the visible answer. Strip it manually with re.sub(r"<think>.*?</think>\s*", "", content, flags=re.DOTALL) if your client doesn't.

Thinking is replayed across turns

Reasoning from earlier assistant turns is replayed back into the prompt, on both runtimes, so the model sees how it reached its previous answers. Reasoning from the turn in progress — including a tool-call chain, where the model calls a tool and then continues after the result — is kept whatever the setting, so it does not lose its own trace mid-flight.

  • Ollama runs the Go template, which renders <think>…</think> for every assistant message that carries reasoning, earlier turns included. This departs from Qwen's stock condition, which drops reasoning from turns before the latest user message. Ollama never passes reasoning to the GGUF's embedded Jinja template, so on Ollama only the Go template decides.
  • llama.cpp runs chat_template.jinja, which replays earlier turns' reasoning by default, as upstream's template does — including upstream's empty <think>\n\n</think> block for an earlier assistant turn that had no reasoning.

Your client has to send the reasoning back. The server returns it — Ollama's /api/chat in the response's thinking field, llama.cpp in reasoning_content — but a template can only replay what arrives in the next request, and each server reads one field:

Endpoint Field read from each assistant message
Ollama /api/chat thinking
Ollama /v1/chat/completions reasoning only — reasoning_content and thinking are dropped silently; an assistant message whose content is an array of parts keeps its reasoning only if it also has tool_calls
llama-server /v1/chat/completions reasoning_content only
LM Studio 0.4.24 /v1/chat/completions reasoning or reasoning_content, passed to the template as reasoning_content; thinking is dropped
KoboldCpp /v1/chat/completions (--jinja) whatever the client sent — the template sees the message unchanged

For loaders like KoboldCpp that pass the message through, chat_template.jinja reads the reasoning from reasoning, then reasoning_content, then thinking (the Janus sibling's order); upstream's template read only reasoning_content, so a trace sent as reasoning or thinking became an empty <think> block. Checked on 2026-09-14 by rendering both templates with KoboldCpp's own Jinja setup (from its koboldcpp.py, concedo branch 4ac5721) — the old one dropped both fields, the new one replays them — and through LM Studio 0.4.24 with a 260K-parameter model on its CPU runtime, where the old and new templates rendered the same because LM Studio normalises the field first.

Checked live on 2026-09-13 with the restamped blobs, CPU only: Ollama 0.33.3 replayed thinking on /api/chat (232 prompt tokens with it, 187 without) and reasoning on /v1 (215 vs 187, measured with the Janus sibling's blob, whose Go template is byte-identical) and ignored reasoning_content on /v1 (187); llama-server master rendered reasoning_content and dropped a reasoning field. That Ollama's /v1 also drops thinking, and the array-content rule, come from Ollama's v0.33.3 and v0.34.0 source, and still hold on the current release: openai/openai.go and server/prompt.go are byte-identical from v0.33.3 through v0.34.2 (source, 2026-09-17).

Which llama.cpp an Ollama carries. 0.33.3 and 0.34.0 bundle b10760; 0.34.1 bundles b10864 and 0.34.2 b10969 (each release's LLAMA_CPP_VERSION). From 0.34.1 on that is a b10763-or-later llama-server — the family that passes preserve_reasoning=true by default — but it changes nothing here: text inference for this model runs Ollama's own Go engine, and the bundled llama.cpp comes into play only when Ollama falls back to its C++ engine, as it does once an mmproj is attached (see Loader compatibility).

The cost is context. Every retained trace stays in the prompt, measured at 181 prompt tokens without / 464 with on a 3-message conversation whose first assistant turn carried a ~240-word trace. Prefill grows with every retained trace, and on a CPU-only host prefill is the slow part. Releases 0.7.0 through 0.8.1 turned replay off for that reason; the CHANGELOG has the history.

To turn it off on llama-server, start it with --no-reasoning-preserve, or per request pass "chat_template_kwargs": {"preserve_thinking": false} (or {"preserve_reasoning": false}). "preserve_thinking": true in a request turns replay back on even against --no-reasoning-preserve. The template reads only preserve_thinking; llama-server derives it from preserve_reasoning and --no-reasoning-preserve. llama.cpp master (b10763 and later) passes preserve_reasoning=true unless started with --no-reasoning-preserve and logs "chat template supports preserving reasoning, it is enabled by default"; b10760 (the build Ollama 0.33.3 bundles) and b10519 pass nothing and log "consider enabling it via --reasoning-preserve", yet still replay, because the template's own default is on. Tested on 2026-09-13 with a 260K-parameter test model and --chat-template-file (the template, not the model, decides the prompt), via /apply-template and /v1/chat/completions, on master 4a89937 (b10941), b10760 and b10519 (LM Studio's avx2 CPU runtime 2.30.0): each of those settings behaved as described, a live tool-call chain kept its reasoning in every mode, and no request returned an error. On the restamped blob itself, with its embedded template and default flags, llama-server master rendered a 3-message conversation in 111 prompt tokens with the earlier turn's reasoning and 81 with --no-reasoning-preserve or either false kwarg (85 with no reasoning sent, for the empty think block), and a real tool-result turn with string arguments returned HTTP 200.

To turn it off on Ollama, edit the assistant branch of the Modelfile / template (both, so make check's bridge sync stays green): change {{ if (and $.IsThinkSet .Thinking) -}} back to Qwen's stock {{ if (and $.IsThinkSet (and .Thinking (or $last (gt $i $lastUserIdx)))) -}} (keep the -}}; the $lastUserIdx loop at the top of the template is there for it). check_go_template.py (check 9) will then fail, since it requires the replay condition. Do not delete the block instead — see Which template Ollama runs.

Disabling thinking

This is a reasoning-first model — it opens a <think> block by default. For a direct answer with no reasoning trace (simple or latency-sensitive calls), turn thinking off:

ollama run hf.co/FoolDev/Thanatos-27B-HERETIC:Q4_K_M --think=false

or send "think": false on /api/chat. With thinking off the model skips the reasoning trace and answers straight into content; with it on (the default) reasoning is emitted into the thinking field.

Reasoning effort

OpenAI-compatible clients can send reasoning_effort. Ollama's /v1 layer accepts eight values. Where chat_template.jinja sees the value as sent (llama.cpp), it maps each to one of three tiers or switches thinking off:

reasoning_effort Effect
high, xhigh, max, ultra xhigh — the model is told to think carefully and check its assumptions
medium, unset, null, anything unrecognised medium — the default; no effort instruction is added
low, minimal low — the model is told to keep its thinking brief
none thinking off for that request, the same as "think": false

The default changed in 0.12.4. Upstream's template treated an unset value as xhigh and raised on anything it did not recognise; this template resolved unknown values rather than raising, but kept the xhigh default — so the same request answered 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, and xhigh is something a client asks for explicitly. Upstream accepted only xhigh, medium and low, so everything else failed — including high, the value most OpenAI-compatible clients send; on Ollama, while it ran the embedded template, that surfaced as HTTP 500.

On Ollama's default path the Go template runs instead (see Which template Ollama runs), and it adds the same instruction from Ollama's think level. Ollama folds the eight values into four levels before any template sees them, so the Go template maps levels, not values: high, xhigh, max and ultra get the xhigh line; low and minimal get the low line; medium gets no line; none switches thinking off. Ollama sends an unset value as medium, indistinguishable from an explicit medium, so the default adds no line — which since 0.12.4 is exactly what chat_template.jinja does too. Send high for the xhigh line on either path. "think": "high", "low" and "max" on /api/chat map the same way. Mixed case such as "HIGH" is rejected by Ollama itself with HTTP 400 before any template runs. Verified on Ollama 0.33.3 and 0.34.0 with scripts/live_check.sh, which sends /api/chat think levels: on a one-line chat, high and max each added 38 prompt tokens and low 26, while medium and an unset value added none. The /v1 value-to-level mapping above comes from Ollama's source, unchanged from v0.33.3 through v0.34.2 (2026-09-17).

On llama.cpp every value except none reaches chat_template.jinja as sent; llama-server handles none itself by switching thinking off. Tested on llama.cpp master a2878d3: all eight values, "HIGH" and an unknown word each returned HTTP 200, and the rendered prompt carried the tier in the table, the xhigh line included when no value was sent. The template lower-cases and trims the value itself. LM Studio's CPU engine (llama.cpp b10519) passed the same checks; the LM Studio app's own API layer is untested.

Which template Ollama runs

This repo ships two templates for the same ChatML model: the Go template (served by HF's Ollama bridge) / Modelfile TEMPLATE, and chat_template.jinja, stamped into the GGUF. They do not render identical prompts — the Go template pins JSON tool calls and takes the reasoning-effort line from Ollama's think level, while the Jinja template has its own tool instructions, uses the XML <function=…> form unless a replayed call carries string arguments, and takes the line from the request's reasoning_effort. Ollama 0.33.3 and 0.34.0 (both verified) pick one at load time: they prefer the embedded template when that advertises more capabilities — tools, thinking — and keep the Go template when they tie. They tie, so Ollama runs the Go template; llama.cpp and LM Studio run the embedded Jinja template. That selection code is unchanged through v0.34.2 (source, 2026-09-17), and Ollama's built-in renderers — it ships a qwen35 one — displace both templates only when the model's own config names a renderer or parser, which this repo's does not. Ollama logs the choice:

msg="template selection" ... selected=go_template go_template="[completion tools thinking]"

The Go template's "thinking" entry comes from its single .Thinking reference — the block that renders assistant reasoning into the prompt. Removing it drops that capability: Ollama switches to the embedded template, a single tool call can come back twice (seen once, on the Janus sibling; cause inferred, not confirmed — see Known limitations), and reasoning_effort suddenly selects the Jinja template's tiers — under upstream's version of that template it raised instead, which is what caused the HTTP 500s. That happened once; the CHANGELOG has the details. Setting OLLAMA_GO_TEMPLATE=1 or =0 on the Ollama server forces one template or the other for every model.

Tool / function calling

The model emits whichever shape the system prompt prescribes. The Ollama path pins one — a JSON object inside <tool_call> tags. On llama.cpp, chat_template.jinja chooses per conversation: XML (the model's native form) when every tool argument is a mapping, JSON when a replayed call carries string arguments — the case upstream's XML-only template cannot render (below).

Ollama path (this repo's Modelfile). The TEMPLATE directive prompts the model to emit JSON-in-XML, the form Ollama's tool-call extractor parses into a structured tool_calls array. After make build, ollama show thanatos-27b lists tools and thinking under Capabilities, and both /api/chat and /v1/chat/completions accept a tools array. The tool definitions go in with {{ json .Function }}: on Ollama 0.14 and later a bare {{ .Function }} prints Go struct syntax instead of JSON (ollama/ollama#14601; fix proposed in ollama/ollama#18391).

<tool_call>
{"name": "get_current_weather", "arguments": {"city": "Paris", "unit": "celsius"}}
</tool_call>

Embedded-jinja path (llama.cpp, llama-cpp-python, LM Studio). The bundled GGUF already embeds chat_template.jinja, so this path works as-is. It is the upstream Qwen 3.8 template — what you get from a quant pulled straight from mradermacher/Qwen3.8-27B-heretic-ara-GGUF — that emits the more verbose XML form:

<tool_call>
<function=get_current_weather>
<parameter=city>
Paris
</parameter>
<parameter=unit>
celsius
</parameter>
</function>
</tool_call>

Keep XML on llama-server. llama-server recognises this template as Qwen3-Coder-style and parses the model's reply with a parser — and, once the model opens <tool_call>, a grammar — that accept only <function=…><parameter=…>. A prompt that teaches the JSON form gets replies that parser cannot read. 0.7.0 to 0.9.0 defaulted to auto; 0.10.0 went back to xml for that reason.

Replayed tool calls render on llama-server. Every OpenAI-compatible client hands assistant tool calls back with function.arguments as a JSON string, and the XML parameter form needs the individual key/value pairs — a chat template has tojson but no inverse. llama-server decodes the string into a mapping before it renders the template, so the second turn of a tool call renders in XML with no error. Tested on 2026-09-14 with a 260K-parameter test model and this template, replaying a call whose arguments was the string {"city": "Paris"}: /apply-template and /v1/chat/completions returned HTTP 200 with the call rendered as <parameter=city> on llama.cpp master 97e4ca7, b10760 (the llama-server Ollama 0.33.3 bundles) and b10519 (LM Studio's avx2 CPU runtime 2.30.0).

Other loaders, checked 2026-09-14. KoboldCpp v1.120 renders the template with Python's jinja2 only when run with --jinja --jinja_tools (without --jinja_tools, requests with tools skip the template), and parses JSON-string arguments with json.loads first: with this template and a string-argument replay it returned HTTP 200 and rendered the call as XML (tested with a 260K-parameter model). Jan runs llama.cpp's own server code — v0.8.4 downloads llama-server from the janhq/llama.cpp fork (b9967), and its main branch links ggml-org llama.cpp b10809 — so it inherits the conversion above (from source, untested). The LM Studio app renders templates with its own JavaScript Jinja engine rather than llama-server's, and also parses the arguments first: through LM Studio 0.4.24's /v1/chat/completions (CPU runtime avx2 2.37.0, a 260K-parameter model carrying this template) a string-argument replay returned HTTP 200 with the call rendered as XML, and arguments that were not valid JSON returned HTTP 500 before the template ran, with or without tool_call_format: "auto". If a loader does raise, pass tool_call_format: "auto" in its template kwargs, or change 'xml' to 'auto' in the template's _tool_format default (in LM Studio, under the model's Prompt Template setting).

A loader that hands the template the raw string instead cannot use the XML form. Upstream's template applies the items filter to it and dies:

Can only get item pairs from a mapping.

chat_template.jinja is the base model's own template with two deliberate changes, each marked CHANGED FROM UPSTREAM in the file. The one that matters here: it gains a tool_call_format setting — xml (the default), auto, or json. xml renders exactly upstream's form, and raises a message that says what to do if it receives string arguments. auto, the opt-in for loaders that pass them through, renders the prompt in JSON form whenever a historical tool call carries string arguments and is byte-identical to upstream's XML when every argument is a mapping — mirroring what the Qwen 3.8 "safe" template does. Pass it with --chat-template-kwargs '{"tool_call_format":"auto"}' or a request's chat_template_kwargs. On llama-server auto changes nothing, because arguments reach the template as mappings; do not set json there, for the parser reason above. The other two: reasoning_effort is normalised instead of validated (see Reasoning effort), and an earlier turn's reasoning is read from reasoning, then reasoning_content, then thinking (see Thinking is replayed across turns). The vision handling and every other upstream behaviour are unchanged, including replaying earlier turns' reasoning by default (see Thinking is replayed across turns).

llama-server -m Thanatos-27B.Q4_K_M.gguf \
  --jinja --chat-template-file chat_template.jinja

With that flag the same client code should work against either server, as long as both return a parsed tool_calls array — verified on Ollama and on standalone llama-server (llama.cpp master a2878d3, under the earlier auto default); the prompt format underneath still differs, as above.

The bundled GGUF already carries this template — you do not need the flag. Thanatos-27B.Q4_K_M.gguf has chat_template.jinja stamped into its metadata, so a loader that reads the embedded template and parses Qwen's <tool_call> output gets working tool calls with no configuration: llama-server (tested on llama.cpp master a2878d3 and b10760) and LM Studio's CPU runtime (llama.cpp b10519, tested). llama-cpp-python renders the tools but, per its source, does not parse <tool_call> output into tool_calls. KoboldCpp (with --jinja --jinja_tools) was tested and Jan checked from source; see above. ./scripts/build.sh (make build) stamps it into every quant it builds, too. The --chat-template-file flag above is only needed for a GGUF that was not built or stamped that way — a quant pulled straight from upstream, for instance. To restamp one you already have:

bash python3 scripts/strip_mtp.py IN.gguf OUT.gguf --chat-template chat_template.jinja

On an already-MTP-clean input that rewrites the file purely to swap the template; tensor data is copied byte-for-byte either way, so there is no re-quantization.

End-to-end exercise (Ollama path):

python examples/ollama_chat.py        # section 3 runs a real round-trip

Known limitations

  • On the embedded-template path, a single tool call can come back twice. On the Janus sibling, when it shared this dense Qwen 3.8 27B base, Ollama 0.33.3 running the embedded template returned a first-turn tool call twice in one test. A separate raw generation on Janus showed the model drafting the call inside <think> before emitting it once after </think>, so the parser most likely matched such a draft (inferred, not confirmed). On standalone llama.cpp master (a2878d3, CPU) it did not reproduce on either model: six first-turn calls per model (three prompts, with and without parallel_tool_calls) and a streamed run each came back exactly once. Nor did it on Janus under b10760, the llama.cpp build Ollama 0.33.3 bundles and the one the duplicate came through. LM Studio's CPU engine (llama.cpp b10519) passed the same checks; the LM Studio app's own API layer is untested. On Ollama's default path — the Go template, see Which template Ollama runs — a first-turn tool call on this model returned exactly one call. If your client executes tools wherever the embedded template renders (llama.cpp, LM Studio, or Ollama with OLLAMA_GO_TEMPLATE=0), drop identical consecutive calls.
  • Dense, so every parameter runs on every token. There is no expert sparsity to hide behind: the ~16 GiB weights-plus-compute footprint is a floor, not an average, and per-token throughput is what a full 27B forward pass costs. Trim num_ctx to fit a smaller host — there is no expert-offload trick to fall back on. (The Janus sibling went back to being a ~3B-active MoE in its 0.9.0, so it does offer a cheaper per-token forward pass — at a larger resident footprint.)
  • No mmproj in this release, and vision via Ollama is broken upstream (the qwen35/qwen35moe arch entries are present in Ollama's Go engine but missing from the C++ llama.cpp fallback Ollama uses when mmproj is attached — see the Vision section). For image input use llama.cpp directly until that's fixed.
  • Q4_K_M quality loss is real. Use Q5_K_M or Q6_K if you have the VRAM (~20-22 GB).
  • Large tool-call arguments can be dropped. Ollama's JSON-in-XML tool format makes the model JSON-escape the entire arguments object inline; for a big/complex payload (e.g. a file's content in a write_file call) the model can fail to escape it, so the field arrives undefined and the call fails. Qwen's native <function=…><parameter=…> format (raw values, no escaping) was tested as a fix but parses unreliably through Ollama, so the Go template deliberately keeps JSON-in-XML (chat_template.jinja keeps the native form; see Tool / function calling). Mitigation: write large files in smaller pieces per call.
  • No formal evaluation in this card. Most numbers are estimates; the make bench tok/s figures are measured: 5.01 at Q4_K_M on CPU for the current blob, and ~12.3 at Q3_K_S / ~9.3 at Q4_K_M under Vulkan on the May 2026 pre-rebase builds.
  • Uncensored base. The heretic-ara abliteration dials back the refusal-training of upstream Qwen 3.8. Outputs may be more compliant with sensitive requests than the vanilla base; the Thanatos system prompt still steers behavior, but the safety floor is lower. Apply your own filtering for user-facing deployments.

Related models

Model Notes
heretic-org/Qwen3.8-27B-heretic-ara Immediate base, safetensors
mradermacher/Qwen3.8-27B-heretic-ara-GGUF Recommended GGUF source (what make build pulls from)
cygnal/Qwen3.8-27B-heretic-ara-Q4_K_M-MTP-GGUF Same heretic-ara abliteration but keeps the MTP head (for the llama.cpp MTP path / speculative decoding)
Qwen/Qwen3.8-27B Upstream pre-Heretic base, safetensors
unsloth/Qwen3.8-27B-GGUF Pre-Heretic GGUF mirror + reference mmproj-F16.gguf projector
FoolDev/Janus-35B-HERETIC Sibling, and since its 0.9.0 a different model: llmfan46's Qwen 3.6 35B-A3B MoE (~34.7B total / ~3B active), abliterated MPOA-style rather than ARA. Larger resident footprint, much smaller KV cache; the -35B- slug is accurate.
Crownelius/Crow-9B-HERETIC-4.6 9B starter model when 27B is too heavy

Credits

License inherited from upstream: Apache-2.0.

Identity and Version

Repository
FoolDev/Thanatos-27B-HERETIC
Publisher
Fool
Task
Text generation
Modality
Text
Library
gguf
Parameters
Not stated by the source
Languages
en, zh, ru, es, fr, it, ja, ko
Revision
8f009ecb63be7022324094a88e114212ef77501f
First published
2026-05-02
Last updated
2026-09-18

Files and Weights

40 files, 16.5 GB in total. The weights are 1 file totalling 16.5 GB in gguf.

Weights1 file · 16.5 GB
Configuration11 files · 71.5 KB
Documentation4 files · 263.7 KB
Other22 files · 247.5 KB
Repository2 files · 2.5 KB
Every file
FileTypeSizeSHA-256
Thanatos-27B.Q4_K_M.ggufWeights16.5 GB 24a2d3a21b5a
examples/llama_cpp_quickstart.pyConfiguration2.9 KB
examples/llama_cpp_vision.pyConfiguration5.2 KB
examples/ollama_chat.pyConfiguration6.4 KB
examples/transformers_quickstart.pyConfiguration4.3 KB
scripts/check_bridge_sync.pyConfiguration7.9 KB
scripts/check_chat_template.pyConfiguration13.1 KB
scripts/check_go_template.pyConfiguration9.9 KB
scripts/heretic_unattended.pyConfiguration6.0 KB
scripts/rename_arch.pyConfiguration2.5 KB
scripts/strip_mtp.pyConfiguration7.2 KB
scripts/verify_arch.pyConfiguration6.1 KB
CHANGELOG.mdDocumentation179.6 KB
LICENSEDocumentation11.3 KB
README.mdDocumentation68.0 KB
examples/README.mdDocumentation4.9 KB
CITATION.cffOther1.7 KB
MakefileOther3.3 KB
ModelfileOther13.4 KB
banner.pngOther20.7 KB
banner.svgOther7.2 KB
bmc-banner.svgOther5.4 KB
chat_template.jinjaOther17.8 KB
dense-flow.pngOther77.6 KB
dense-flow.svgOther39.0 KB
paramsOther178 B
scripts/bench.shOther4.0 KB
scripts/build.shOther5.4 KB
scripts/cap_ctx.shOther2.7 KB
scripts/check.shOther11.2 KB
scripts/fetch_vision.shOther2.8 KB
scripts/heretic_remote.shOther5.1 KB
scripts/install-hooks.shOther680 B
scripts/live_check.shOther12.9 KB
scripts/load_bundle.shOther6.6 KB
scripts/smoke_test.shOther7.0 KB
systemOther676 B
templateOther2.1 KB
.gitattributesRepository1.6 KB
.gitignoreRepository927 B

License and Download

License
apache-2.0
Access
Open weights, no gate
Download size
16.5 GB
Download from Fool

Released by Fool through its official repository on Hugging Face. Read the license.

Built From

  • Derived from heretic-org/Qwen3.8-27B-heretic-ara
  • Quantized from heretic-org/Qwen3.8-27B-heretic-ara
  • Trained on (disclosed) Crownelius/Creative_Writing_ShareGPT_Enhanced
  • Trained on (disclosed) Crownelius/Crow-Heretic-TeichAI-Unified
  • Trained on (disclosed) Crownelius/Opus-4.7-Reasoning
  • Trained on (disclosed) microsoft/rStar-Coder
  • Trained on (disclosed) openbmb/UltraData-Math
  • Trained on (disclosed) peteromallet/dataclaw-peteromallet

Memory Requirements

PrecisionWeights in memory
As published16.5 GB

Weights only, from the published parameter count; the key-value cache and runtime add to this.

Questions About Thanatos-27B-HERETIC

Can I use Thanatos-27B-HERETIC commercially?

Yes. Thanatos-27B-HERETIC is released under Apache License 2.0. The Apache License 2.0 is a permissive open-source license. It permits commercial use, modification and redistribution. It requires keeping the license and copyright notices and any NOTICE file, stating significant changes, and it includes an express patent grant from contributors.

Similar Models

Fine-tune Qwen3 (14B) for free using our Google Colab notebook! - Read our Blog about Qwen3 support: unsloth.ai/blog/qwen3 - View the rest of our notebooks in our docs here. Qwen3-Coder is available in multiple sizes. Today, we're excited to introduce Qwen3-Coder-30B-A3B-Instruct. This streamlined model maintains impressive performance and efficiency, featuring the following key enhancements: - Significant Performance among open models on Agentic Coding, Agentic Browser-Use, and other foundational coding tasks. - Long-context Capabilities with native support for 256K tokens, extendable up to 1M tokens using Yarn, optimized for repository-scale understanding. - Agentic Coding supporting for…

Open weights apache-2.0 transformers

Model · Text generation

opt-125m

AI at Meta

OPT was first introduced in Open Pre-trained Transformer Language Models and first released in metaseq's repository on May 3rd 2022 by Meta AI. Disclaimer: The team releasing OPT wrote an official model card, which is available in Appendix D of the paper. Content from this model card has been written by the Hugging Face team. To quote the first two paragraphs of the official paper OPT was predominantly pretrained with English text, but a small amount of non-English data is still present within the training corpus via CommonCrawl. The model was pretrained using a causal language modeling (CLM) objective. OPT belongs to the same family of decoder-only models like GPT-3. As such, it was…

Open weights other 2,048 tokens transformers

Model · Text generation

Ornith-1.5-9B-GGUF

Ornith

Chirp Chirp! We are introducing Ornith-1.5, a major step toward building foundation models through end-to-end self-improvement. Ornith-1.5 extends Ornith-1.0 (which was developed on top of Qwen3.5 and Gemma4 with additional continued pretraining, mid-training, and post-training) by expanding the self-improvement loop from scaffold and rollout optimization to jointly optimizing task generation, scaffold construction, and solution rollouts. Rather than relying on a fixed set of human-curated tasks and manually designed harnesses, Ornith-1.5 continuously generates new training tasks, discovers effective strategies for solving them, and improves the policy through reinforcement learning. For…

Open weights mit transformers

Model · Text generation

Ornith-1.5-35B-A3B-GGUF

Ornith

Chirp Chirp! We are introducing Ornith-1.5, a major step toward building foundation models through end-to-end self-improvement. Ornith-1.5 extends Ornith-1.0 (which was developed on top of Qwen3.5 and Gemma4 with additional continued pretraining, mid-training, and post-training) by expanding the self-improvement loop from scaffold and rollout optimization to jointly optimizing task generation, scaffold construction, and solution rollouts. Rather than relying on a fixed set of human-curated tasks and manually designed harnesses, Ornith-1.5 continuously generates new training tasks, discovers effective strategies for solving them, and improves the policy through reinforcement learning. For…

Open weights mit transformers

Model · Text generation

Ornith-1.0-9B-GGUF

Ornith

Aloha! Today, we are releasing Ornith-1.0, a self-improving family of open-source models for agentic coding. This model card documents Ornith-1.0-9B, the most lightweight member of the Ornith family, designed for efficient single-GPU deployment. Ornith-1.0-9B is a dense ~9B model (≈19 GB in bf16), so it serves comfortably on a single 80GB GPU. The recipes below stand up an OpenAI-compatible server; add --tensor-parallel-size / --tp if you want to shard across more GPUs. For a quick local test (or to script offline generation), load the model directly with Transformers. Make sure you have a recent release installed — see the Transformers installation guide; Ornith-1.0-9B requires…

Open weights mit transformers

Uncensored Qwen3.8-27B, published as GGUF quantizations with the multi token prediction (MTP) head retained and verified. Refusal behaviour has been substantially reduced, not eliminated. See Measured behaviour for the numbers. Capabilities, training data, and architecture are otherwise unchanged. - Refusal directions removed with Heretic, which co minimizes refusal count against KL divergence from the base model. No handwritten refusal removal code, no finetuning, no additional training data. - Abliteration runs at bf16 (no 4 bit quantization). the resulting LoRA is merged into the bf16 base, so the published weights are not a quantized round trip. - mtp. tensors are copied verbatim from…

Open weights apache-2.0 llama.cpp