Text Generation
GGUF
Safetensors
English
Turkish
plc
iec-61131-3
structured-text
code-generation
ollama
mikrodev
ALB
AdvanceLogicBuilder
MikrodevLogicStudio
advance-logic-builder
mikrodev-logicstudio
gemma
conversational
Instructions to use Mikrodev/stcoder-gemma4-12b-gguf 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 Mikrodev/stcoder-gemma4-12b-gguf 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 Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M # Run inference directly in the terminal: llama cli -hf Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M # Run inference directly in the terminal: llama cli -hf Mikrodev/stcoder-gemma4-12b-gguf: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 Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf Mikrodev/stcoder-gemma4-12b-gguf: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 Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
Use Docker
docker model run hf.co/Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use Mikrodev/stcoder-gemma4-12b-gguf with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Mikrodev/stcoder-gemma4-12b-gguf" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Mikrodev/stcoder-gemma4-12b-gguf", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
- Ollama
How to use Mikrodev/stcoder-gemma4-12b-gguf with Ollama:
ollama run hf.co/Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
- Unsloth Studio
How to use Mikrodev/stcoder-gemma4-12b-gguf with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Mikrodev/stcoder-gemma4-12b-gguf to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for Mikrodev/stcoder-gemma4-12b-gguf to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for Mikrodev/stcoder-gemma4-12b-gguf to start chatting
- Docker Model Runner
How to use Mikrodev/stcoder-gemma4-12b-gguf with Docker Model Runner:
docker model run hf.co/Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
- Lemonade
How to use Mikrodev/stcoder-gemma4-12b-gguf with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull Mikrodev/stcoder-gemma4-12b-gguf:Q4_K_M
Run and chat with the model
lemonade run user.stcoder-gemma4-12b-gguf-Q4_K_M
List all available models
lemonade list
- Atomic Chat
| <title>stcoder-gemma4-12b — Mikrodev STCoder for PLC Structured Text</title> | |
| <style> | |
| :root{--bg:#e9edf1;--paper:#fff;--paper2:#f4f7f9;--rule:#d3dae1;--ink:#131a21;--mid:#5c6b78;--dim:#8b98a5; | |
| --teal:#4a5f86;--teal-soft:#d6eef1;--teal-line:#93cfd7;--amber:#9a6512;--amber-soft:#f2e4c9;--amber-line:#d8bb84; | |
| --red:#ac3527;--red-soft:#f6dcd7;--code-bg:#f6f8fa;--code-ink:#1e2831; | |
| --shadow:0 1px 1px rgba(19,26,33,.04),0 8px 26px rgba(19,26,33,.06);} | |
| @media (prefers-color-scheme:dark){:root{--bg:#0b1014;--paper:#131b22;--paper2:#18222a;--rule:#25313b; | |
| --ink:#d5dee6;--mid:#8c9aa7;--dim:#5f6d79;--teal:#7d97c9;--teal-soft:#10353a;--teal-line:#1d5b65; | |
| --amber:#d09a3f;--amber-soft:#382c12;--amber-line:#6b5322;--red:#e07963;--red-soft:#3d1d17; | |
| --code-bg:#0e161c;--code-ink:#c2ccd6;--shadow:0 1px 1px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.34);}} | |
| :root[data-theme=light]{--bg:#e9edf1;--paper:#fff;--paper2:#f4f7f9;--rule:#d3dae1;--ink:#131a21;--mid:#5c6b78; | |
| --dim:#8b98a5;--teal:#4a5f86;--teal-soft:#d6eef1;--teal-line:#93cfd7;--amber:#9a6512;--amber-soft:#f2e4c9; | |
| --amber-line:#d8bb84;--red:#ac3527;--red-soft:#f6dcd7;--code-bg:#f6f8fa;--code-ink:#1e2831; | |
| --shadow:0 1px 1px rgba(19,26,33,.04),0 8px 26px rgba(19,26,33,.06);} | |
| :root[data-theme=dark]{--bg:#0b1014;--paper:#131b22;--paper2:#18222a;--rule:#25313b;--ink:#d5dee6;--mid:#8c9aa7; | |
| --dim:#5f6d79;--teal:#7d97c9;--teal-soft:#10353a;--teal-line:#1d5b65;--amber:#d09a3f;--amber-soft:#382c12; | |
| --amber-line:#6b5322;--red:#e07963;--red-soft:#3d1d17;--code-bg:#0e161c;--code-ink:#c2ccd6; | |
| --shadow:0 1px 1px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.34);} | |
| *{box-sizing:border-box} | |
| body{margin:0;background:var(--bg);color:var(--ink);line-height:1.6;-webkit-font-smoothing:antialiased; | |
| font-family:"Segoe UI",system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif} | |
| .sheet{max-width:1080px;margin:0 auto;padding:clamp(18px,3.5vw,50px) clamp(14px,3.5vw,42px) 90px} | |
| code,pre,.mono{font-family:"Cascadia Code","JetBrains Mono",Consolas,ui-monospace,SFMono-Regular,Menlo,monospace} | |
| .num,table,.bar b{font-variant-numeric:tabular-nums} | |
| .mast{border-top:3px solid var(--ink);padding-top:14px} | |
| .kick{display:flex;flex-wrap:wrap;gap:13px;align-items:baseline;font-family:"Cascadia Code",Consolas,monospace; | |
| font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--mid)} | |
| .kick .id{color:var(--teal);font-weight:700} | |
| h1{font-size:clamp(28px,5vw,47px);line-height:1.03;letter-spacing:-.025em;margin:.3em 0 .16em;font-weight:800; | |
| text-wrap:balance;max-width:24ch} | |
| .sf{font-size:clamp(16px,2vw,19px);color:var(--mid);max-width:66ch;margin:0} | |
| .sf b{color:var(--ink);font-weight:650} | |
| .spec{display:grid;grid-template-columns:repeat(auto-fit,minmax(128px,1fr));gap:1px;background:var(--rule); | |
| border:1px solid var(--rule);border-radius:10px;overflow:hidden;margin:28px 0 0;box-shadow:var(--shadow)} | |
| .spec div{background:var(--paper);padding:12px 14px} | |
| .spec .k{font-family:"Cascadia Code",Consolas,monospace;font-size:10px;letter-spacing:.13em; | |
| text-transform:uppercase;color:var(--dim)} | |
| .spec .v{font-size:19px;font-weight:750;letter-spacing:-.01em;margin-top:3px;word-break:break-word} | |
| .spec .v small{font-size:11.5px;font-weight:500;color:var(--mid);letter-spacing:0;display:block} | |
| section{margin-top:clamp(42px,6vw,70px)} | |
| .sh{display:flex;gap:13px;align-items:baseline;border-bottom:1px solid var(--rule);padding-bottom:9px;margin-bottom:18px} | |
| .sh .sn{font-family:"Cascadia Code",Consolas,monospace;font-size:12px;font-weight:700;color:var(--teal);letter-spacing:.08em} | |
| h2{font-size:clamp(19px,2.6vw,26px);margin:0;letter-spacing:-.015em;font-weight:750;text-wrap:balance} | |
| h3{font-size:16px;margin:24px 0 7px;font-weight:700} | |
| p{max-width:70ch}p.lead{color:var(--mid)} | |
| ul,ol{max-width:72ch;padding-left:20px}li{margin:.3em 0} | |
| .card{background:var(--paper);border:1px solid var(--rule);border-radius:12px;padding:18px 20px; | |
| margin:18px 0;box-shadow:var(--shadow)} | |
| .card.hi{border-left:3px solid var(--teal)} | |
| .card.warn{border-left:3px solid var(--red)} | |
| .card h3{margin-top:0} | |
| .tw{overflow-x:auto;border:1px solid var(--rule);border-radius:11px;box-shadow:var(--shadow);margin:18px 0} | |
| table{border-collapse:collapse;width:100%;background:var(--paper);font-size:13.2px;min-width:600px} | |
| th,td{padding:10px 13px;text-align:right;border-bottom:1px solid var(--rule);vertical-align:top} | |
| th:first-child,td:first-child{text-align:left} | |
| thead th{background:var(--paper2);font-size:10.4px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);font-weight:700} | |
| tbody tr:last-child td{border-bottom:none} | |
| td.ft{color:var(--teal);font-weight:700}td.bs{color:var(--amber);font-weight:650} | |
| .pick{display:grid;gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:11px; | |
| overflow:hidden;box-shadow:var(--shadow)} | |
| .pick .row{background:var(--paper);padding:14px 17px;display:grid;grid-template-columns:120px 150px 1fr;gap:14px;align-items:start} | |
| @media(max-width:700px){.pick .row{grid-template-columns:1fr;gap:5px}} | |
| .pick .vram{font-weight:750;font-size:15px} | |
| .pick .q{font-family:"Cascadia Code",Consolas,monospace;font-size:12.6px;font-weight:700;color:var(--teal)} | |
| .pick .q small{display:block;color:var(--mid);font-weight:400;font-size:11px} | |
| .pick .why{color:var(--mid);font-size:13.5px} | |
| .pick .why em{color:var(--amber);font-style:normal;font-weight:650} | |
| .steps{counter-reset:s;list-style:none;padding:0} | |
| .steps li{counter-increment:s;position:relative;padding-left:38px;margin-bottom:16px} | |
| .steps li::before{content:counter(s);position:absolute;left:0;top:0;width:25px;height:25px;border-radius:50%; | |
| background:var(--teal-soft);color:var(--teal);font-family:"Cascadia Code",Consolas,monospace;font-size:12px; | |
| font-weight:700;display:flex;align-items:center;justify-content:center} | |
| .steps b{display:block;font-size:14.5px} | |
| .steps p{margin:.2em 0;color:var(--mid);font-size:13.5px} | |
| pre{margin:9px 0 0;padding:12px 14px;overflow-x:auto;background:var(--code-bg);color:var(--code-ink); | |
| font-size:12.3px;line-height:1.55;border-radius:8px;border:1px solid var(--rule)} | |
| pre.plain{border-radius:0;border:none;margin:0} | |
| pre .bad{background:var(--red-soft);color:var(--red);font-weight:700;border-radius:3px;padding:0 2px} | |
| pre .good{background:var(--teal-soft);color:var(--teal);font-weight:700;border-radius:3px;padding:0 2px} | |
| .pgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(292px,1fr));gap:13px} | |
| .pc{background:var(--paper);border:1px solid var(--rule);border-radius:11px;padding:14px 16px;box-shadow:var(--shadow)} | |
| .pc .q{font-size:14px;font-weight:650;margin-bottom:6px} | |
| .pc .q::before{content:"\201C"}.pc .q::after{content:"\201D"} | |
| .pc .e{font-size:13px;color:var(--mid)} | |
| .pc .n{font-size:12.2px;color:var(--dim);margin-top:6px;font-style:italic} | |
| .fig{background:var(--paper);border:1px solid var(--rule);border-radius:12px;padding:19px 21px 15px; | |
| margin:20px 0;box-shadow:var(--shadow)} | |
| .fc{font-family:"Cascadia Code",Consolas,monospace;font-size:10.5px;letter-spacing:.14em;text-transform:uppercase; | |
| color:var(--dim);margin-bottom:4px} | |
| .ftl{font-weight:700;font-size:15px;margin-bottom:13px} | |
| .legend{display:flex;flex-wrap:wrap;gap:15px;font-size:12.3px;color:var(--mid);margin:0 0 14px} | |
| .legend span{display:inline-flex;align-items:center;gap:6px} | |
| .sw{width:11px;height:11px;border-radius:2px}.sw.b{background:var(--amber)}.sw.f{background:var(--teal)} | |
| .sw.r{background:var(--red)} | |
| .frow{display:grid;grid-template-columns:180px 1fr;gap:13px;align-items:center;margin-bottom:14px} | |
| @media(max-width:640px){.frow{grid-template-columns:1fr;gap:5px}} | |
| .flab{font-size:13px;font-weight:650} | |
| .flab small{display:block;font-weight:400;color:var(--mid);font-size:10.6px;font-family:"Cascadia Code",Consolas,monospace} | |
| .fbars{display:flex;flex-direction:column;gap:5px} | |
| .bar{position:relative;height:23px;background:var(--paper2);border:1px solid var(--rule);border-radius:5px;overflow:hidden} | |
| .bar>i{position:absolute;inset:0 auto 0 0;display:block} | |
| .bar.base>i{background:var(--amber)}.bar.base2>i{background:var(--amber);opacity:.6} | |
| .bar.ft>i{background:var(--teal)}.bar.ft2>i{background:var(--teal);opacity:.6} | |
| .bar>b{position:absolute;right:8px;top:0;height:23px;display:flex;align-items:center;font-size:11.4px; | |
| font-weight:700;color:var(--ink)} | |
| .strip{display:flex;gap:2px;height:26px} | |
| .strip i{flex:1 1 0;border-radius:1.5px;min-width:3px} | |
| .strip i.ok{background:var(--teal)}.strip i.vi{background:var(--amber)}.strip i.nc{background:var(--red)} | |
| .diff{display:grid;grid-template-columns:1fr 1fr;gap:14px} | |
| @media(max-width:820px){.diff{grid-template-columns:1fr}} | |
| .panel{background:var(--paper);border:1px solid var(--rule);border-radius:11px;overflow:hidden;box-shadow:var(--shadow)} | |
| .panel.base{border-top:3px solid var(--amber-line)}.panel.ft{border-top:3px solid var(--teal-line)} | |
| .ph{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:9px 13px;border-bottom:1px solid var(--rule)} | |
| .tag{font-family:"Cascadia Code",Consolas,monospace;font-size:10.3px;letter-spacing:.12em;text-transform:uppercase;font-weight:700} | |
| .tag.base{color:var(--amber)}.tag.ft{color:var(--teal)} | |
| .badge{font-family:"Cascadia Code",Consolas,monospace;font-size:10.7px;font-weight:700;padding:3px 8px; | |
| border-radius:99px;white-space:nowrap} | |
| .badge.ok{background:var(--teal-soft);color:var(--teal)}.badge.warn{background:var(--amber-soft);color:var(--amber)} | |
| .pv{display:flex;flex-wrap:wrap;gap:5px;padding:9px 13px;border-top:1px solid var(--rule)} | |
| .pv code{font-size:10.5px;padding:2px 7px;border-radius:99px} | |
| .pv code.v{background:var(--red-soft);color:var(--red)}.pv code.c{background:var(--teal-soft);color:var(--teal)} | |
| .note{font-size:13.2px;color:var(--mid);border-left:2px solid var(--rule);padding-left:13px;margin:12px 0 0;max-width:82ch} | |
| .note b{color:var(--ink);font-weight:650} | |
| .chip{display:inline-block;font-family:"Cascadia Code",Consolas,monospace;font-size:10.3px;font-weight:700; | |
| padding:2px 7px;border-radius:99px} | |
| .chip.ok{background:var(--teal-soft);color:var(--teal)}.chip.vi{background:var(--amber-soft);color:var(--amber)} | |
| .chip.nc{background:var(--red-soft);color:var(--red)} | |
| .sib{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:11px;margin-top:14px} | |
| .sib a{display:block;background:var(--paper);border:1px solid var(--rule);border-radius:10px;padding:12px 14px; | |
| text-decoration:none;color:inherit;box-shadow:var(--shadow)} | |
| .sib a.self{border-color:var(--teal);background:var(--teal-soft)} | |
| .sib .sn2{font-family:"Cascadia Code",Consolas,monospace;font-size:11.6px;font-weight:700;color:var(--teal)} | |
| .sib .sd{font-size:12.6px;color:var(--mid);margin-top:2px} | |
| footer{margin-top:60px;padding-top:19px;border-top:1px solid var(--rule);font-size:12.4px;color:var(--dim);max-width:82ch} | |
| footer code{color:var(--mid);font-size:11.6px} | |
| </style> | |
| <div class="sheet"> | |
| <div class="mast"> | |
| <div class="kick"><span class="id">Mikrodev/stcoder-gemma4-12b-gguf</span><span>Fine-tuned from google/gemma-4-12b-it</span> | |
| <span>apache-2.0</span><span>GGUF · Ollama / llama.cpp</span></div> | |
| <h1>stcoder-gemma4-12b</h1> | |
| <p class="sf">stcoder-gemma4-12b is a Gemma-4-12B-it fine-tune that writes Mikrodev LogicStudio Structured Text as a chat reply. It is published and supported as a chat-only model with no tool calling, and it is the weakest of the four fine-tuning cases in this study, so choose it only when you specifically want a non-Qwen lineage as a second opinion. The line's default is stcoder-qwen25-7b at Q8_0.</p> | |
| <p class="sf" style="font-size:15px;margin-top:10px">This is a fine-tune for the Structured Text coding rules of Advance Logic Builder (ALB), the IEC 61131-3 engineering environment developed by Mikrodev and shipped as Mikrodev LogicStudio. It follows Mikrodev's ST dialect, not generic IEC 61131-3 and not another vendor's conventions.</p> | |
| </div> | |
| <div class="spec"> | |
| <div><div class="k">Base model</div><div class="v" style="font-size:14px">gemma-4-12b-it<small>google/gemma-4-12b-it</small></div></div> | |
| <div><div class="k">Builds</div><div class="v">3<small>Q4_K_M / Q6_K / Q8_0</small></div></div> | |
| <div><div class="k">Smallest</div><div class="v">6.9 GiB<small>Q4_K_M</small></div></div> | |
| <div><div class="k">Recommended</div><div class="v" style="color:var(--teal)">Q8_0<small>11.80 GiB</small></div></div> | |
| <div><div class="k">Dialect-clean</div><div class="v" style="color:var(--teal)">86.7%<small>base: 6.7%</small></div></div> | |
| <div><div class="k">Tool calling</div><div class="v" style="color:var(--red);font-size:15px">Not supported<small>chat only</small></div></div> | |
| </div> | |
| <!-- 1. PICK --> | |
| <section> | |
| <div class="sh"><span class="sn">1</span><h2>Which build should you download?</h2></div> | |
| <p class="lead">Start from the graphics memory you have. Every build below is the same fine-tune; | |
| they differ only in how heavily the weights are compressed.</p> | |
| <div class="pick"><div class="row"><div class="vram">13.8 GiB free VRAM</div><div class="q">Q8_0<small>gemma4_12b-tc.q8_0.gguf · 11.80 GiB</small></div><div class="why">recommended <em>crashed on a 16 GiB card in our own testing at 8k context - if that happens, drop to Q6_K</em></div></div><div class="row"><div class="vram">11.1 GiB free VRAM</div><div class="q">Q6_K<small>gemma4_12b-tc.q6_k.gguf · 9.11 GiB</small></div><div class="why">fine — the build this study ran</div></div><div class="row"><div class="vram">8.9 GiB free VRAM</div><div class="q">Q4_K_M<small>gemma4_12b-tc.q4_k_m.gguf · 6.87 GiB</small></div><div class="why">not recommended</div></div></div> | |
| <div class="card hi"><h3>Short answer</h3> | |
| <p>Download <b>Q8_0</b> (<code>gemma4_12b-tc.q8_0.gguf</code>, 11.80 GiB). | |
| Q8_0 is the smallest build with no practically measurable loss against the trained weights - it is the precision we recommend for real work. Free VRAM needed: about | |
| 13.8 GiB — the file plus the 8192-token context and runtime.</p></div> | |
| <div class="card warn"><h3>Why not Q4_K_M</h3> | |
| <p>Q4_K_M halves the bits per weight again. The only capability we measured across quantisations degraded sharply - tool-call composite 0.816 at Q8_0 against 0.642 at Q4_K_M, measured on the Qwen3.5-9B family, the only one of the four where tool-calling worked at all - and low-bit quantisation is a known source of drift and hallucination on long or unusual requests. Our 15-prompt code study never ran at Q4, so we have no measured code-quality figure for it: treat this as a precaution, not a measured code-quality gap.</p></div> | |
| <p class="note"><b>Also published for this model.</b> The LoRA adapter it was trained as | |
| (<code>lora_adapter/</code> in the same repository) so you can merge it at any | |
| precision or continue training from it, and the f16 merged weights | |
| (f16 is the merged fine-tune at full precision: the reference build, for evaluation or for producing your own quantisations.). Ready-to-run Ollama <code>Modelfile</code>s ship next to every build: | |
| correct chat template, stop tokens, <code>num_ctx 8192</code> — the sequence length this | |
| model was fine-tuned at — and the system prompt.</p> | |
| <div class="tw"><table> | |
| <thead><tr><th>File</th><th>Quantisation</th><th>Size</th><th>Fit on 16 GB</th><th style="text-align:left">Note</th></tr></thead> | |
| <tbody><tr><td><code>gemma4_12b-tc.q4_k_m.gguf</code></td><td class="num">Q4_K_M</td><td class="num">6.87 GiB</td><td class="num">solid</td><td></td></tr><tr><td><code>gemma4_12b-tc.q6_k.gguf</code></td><td class="num">Q6_K</td><td class="num">9.11 GiB</td><td class="num">solid</td><td>tested in this study</td></tr><tr><td><code>gemma4_12b-tc.q8_0.gguf</code></td><td class="num">Q8_0</td><td class="num">11.80 GiB</td><td class="num">tight</td><td>recommended</td></tr></tbody> | |
| </table></div> | |
| </section> | |
| <!-- 2. WHO --> | |
| <section> | |
| <div class="sh"><span class="sn">2</span><h2>Is this the right model for you?</h2></div> | |
| <p>Read this first. Tool calling was trained and evaluated for this product line, but it was not usable across the line, so it was dropped as a product decision: stcoder-gemma4-12b is published and supported as a chat-only model. Measured tool-call composite was 0.816 for the Qwen3.5-9B family at Q8_0 (0.805 at Q6_K, 0.642 at Q4_K_M), 0.02-0.06 for the code-focused Qwen2.5-Coder builds, and exactly 0.00 for this Gemma family. | |
| This is the only STCoder build on Google Gemma rather than Qwen, which makes it useful as an architecturally independent cross-check: when two different lineages produce the same block, you have some evidence the request was unambiguous. That is the reason to pick it, and it is the only one supported by the data. | |
| Head-to-head figures from this study (minimal system prompt, 15 prompts per model, greedy decoding at seed 42; ChrF averaged over DELIVERED replies so the denominator is identical across models): | |
| model clean delivery ChrF composite out-tok wall | |
| stcoder-qwen25-7b 93.3% 100% (15/15) 38.5 0.844 340 5.8 s | |
| stcoder-qwen25-14b 100.0% 100% (15/15) 34.9 0.842 288 8.6 s | |
| stcoder-qwen35-9b 93.3% 93.3% (14/15) 40.9 0.845 1227 17.2 s | |
| stcoder-gemma4-12b 86.7% 100% (15/15) 35.9 0.830 401 8.5 s | |
| So its 86.7% clean (13 of 15) is the lowest of the four, and its composite 0.830 is the lowest. Its ChrF is NOT the lowest in this study: 35.9 is above the 14B's 34.9. It is the lowest of the four only in the earlier 2000-prompt evaluation (38-41 there, against 47-52 for the Qwen-based siblings), and those absolute code scores must not be used to rank models against each other: that evaluation's code holdout overlapped the training distribution by about 84%, so its code figures are inflated and weakly discriminative between models. They are cited in this card only for the within-model quant comparison. | |
| Two disclosures that bound every base-versus-fine-tune claim below. First, quantisation was not controlled in the head-to-head: the fine-tune ran at Q6_K (Ollama tag stcoder_gemma4-12b:q6) while the stock base ran the Ollama library default (base tag gemma4:12b). The run data records no quantisation for the base arms, so "effectively Q4" is an inference from the library default, not a recorded value. Second, R24 and clean rates quoted here are computed over the WHOLE reply. Base replies were prose-wrapped 66.7-100% of the time while fine-tune replies were 0-20.0%, so whole-reply scoring flatters the fine-tune; scoring only the fenced code moves the pooled base clean rate from 6.7% to 10.0%, and this model's own base figure from 6.7% (1 of 15) to 20.0% (3 of 15). The gap survives either variant, but the variant has to be named. | |
| This is also the one model where prompting alone nearly closes the gap, and in one respect closes it. Given the whole rule book in the system prompt, the stock base is 73.3% clean (11 of 15) against this fine-tune's 80.0% (12 of 15) - roughly level - and the base's mean R24 in that condition is actually HIGHER than the fine-tune's: 99.80 pooled over all 15 rows, 99.75 over its 12 delivered replies, against the fine-tune's 99.41. The fine-tune still wins the full condition on delivery (15 of 15 against 12 of 15) and on ChrF (32.9 against 24.9 over delivered replies), but if you are willing to paste a long rule preamble every session, the fine-tune buys you much less on this lineage than it does on the other three. | |
| Which one should I download. Default: stcoder-qwen25-7b at Q6_K (5.82 GiB). It is the fastest per answer (5.8 s), the smallest, joint-top on composite (0.844), and highest on ChrF at full delivery (38.5 over 15 of 15). Choose stcoder-qwen25-14b at Q4_K_M (8.37 GiB) on 16 GiB or more if you want the one model that was 100% rule-clean and 0% prose in both conditions, accepting the lowest head-to-head ChrF (34.9) and the slowest generation per token (about 50 tok/s). Choose stcoder-qwen35-9b only if you want the reasoning family, accepting one empty reply in fifteen and the slowest wall time per answer (17.2 s). Choose stcoder-gemma4-12b only for non-Qwen lineage diversity, or because you already run Gemma locally, with the caveats above.</p> | |
| <div class="sib"><a class="" href="https://huggingface.co/Mikrodev/stcoder-qwen25-7b-gguf"><div class="sn2">Mikrodev/stcoder-qwen25-7b-gguf</div><div class="sd">7B · default pick, fastest per answer</div></a><a class="" href="https://huggingface.co/Mikrodev/stcoder-qwen25-14b-gguf"><div class="sn2">Mikrodev/stcoder-qwen25-14b-gguf</div><div class="sd">14B · only one 100% clean in both conditions</div></a><a class="" href="https://huggingface.co/Mikrodev/stcoder-qwen35-9b-gguf"><div class="sn2">Mikrodev/stcoder-qwen35-9b-gguf</div><div class="sd">9B · reasoning family, highest ChrF when it answers</div></a><a class="self" href="https://huggingface.co/Mikrodev/stcoder-gemma4-12b-gguf"><div class="sn2">Mikrodev/stcoder-gemma4-12b-gguf</div><div class="sd">12B · non-Qwen lineage, weakest fine-tune case · you are here</div></a></div> | |
| <div class="fig"> | |
| <div class="fc">Table — the four models on identical measurements</div> | |
| <div class="ftl">Same 15 requests, realistic prompt, greedy decoding. ChrF is averaged over | |
| <i>delivered</i> replies so the denominator is the same for every model.</div> | |
| <div class="tw"><table> | |
| <thead><tr><th>Model</th><th>Clean</th><th>Delivered</th><th>ChrF</th><th>Composite</th> | |
| <th>Out tok</th><th>Per reply</th><th>Recommended build</th></tr></thead> | |
| <tbody><tr><td><code>stcoder-qwen25-7b</code><br><span style="color:var(--dim);font-size:11.5px">7B · default pick, fastest per answer</span></td><td class="num">93.3%</td><td class="num">15/15</td><td class="num">38.5</td><td class="num">0.844</td><td class="num">340</td><td class="num">5.8s</td><td class="num">Q8_0 · 7.54 GiB</td></tr><tr><td><code>stcoder-qwen25-14b</code><br><span style="color:var(--dim);font-size:11.5px">14B · only one 100% clean in both conditions</span></td><td class="num">100.0%</td><td class="num">15/15</td><td class="num">34.9</td><td class="num">0.842</td><td class="num">288</td><td class="num">8.6s</td><td class="num">Q8_0 · 14.62 GiB</td></tr><tr><td><code>stcoder-qwen35-9b</code><br><span style="color:var(--dim);font-size:11.5px">9B · reasoning family, highest ChrF when it answers</span></td><td class="num">93.3%</td><td class="num">14/15</td><td class="num">40.9</td><td class="num">0.845</td><td class="num">1227</td><td class="num">17.2s</td><td class="num">Q8_0 · 9.11 GiB</td></tr><tr><td><code>stcoder-gemma4-12b</code> (this model)<br><span style="color:var(--dim);font-size:11.5px">12B · non-Qwen lineage, weakest fine-tune case</span></td><td class="num ft">86.7%</td><td class="num">15/15</td><td class="num">35.9</td><td class="num">0.83</td><td class="num">401</td><td class="num">8.5s</td><td class="num">Q8_0 · 11.80 GiB</td></tr></tbody> | |
| </table></div> | |
| <p class="note"><b>How to read this.</b> The 14B has the highest clean rate (100%). The 9B has | |
| the highest ChrF among delivered replies (40.9) but delivered on only 14 of 15, so at full | |
| delivery the 7B leads (38.5). Composites for the 9B, 7B and 14B (0.845 / 0.844 / 0.842) are | |
| within noise of each other. Sizes are <b>GiB</b> throughout. | |
| <b>If you are undecided, take stcoder-qwen25-7b at Q8_0.</b></p> | |
| </div> | |
| <div class="card warn"><h3>Read this before you start</h3> | |
| <ul> | |
| <li><b>Chat only — no tool calling.</b> You describe what you need and the model writes | |
| Structured Text. Tool calling was trained and evaluated for this line: the Qwen3.5-9B family | |
| did learn it (tool-call composite 0.816 at Q8_0, falling to 0.642 at Q4_K_M) while the | |
| code-focused families scored 0.02–0.06 and the Gemma family exactly 0.00. Because it was | |
| not usable across the line, <b>tool calling was dropped as a product decision</b> and these | |
| builds are published and supported as chat models. Do not build an agent on them.</li> | |
| <li><b>Always compile before deployment.</b> The model produces dialect-correct code, which is | |
| not the same as correct control logic. Compile it in LogicStudio and review the logic.</li> | |
| <li><b>It is a small local model.</b> Expect to iterate on a prompt, not to receive a finished | |
| project.</li> | |
| </ul> | |
| </div> | |
| </section> | |
| <!-- 3. SETUP --> | |
| <section> | |
| <div class="sh"><span class="sn">3</span><h2>Set-up, step by step</h2></div> | |
| <ol class="steps"><li><b>Install Ollama</b><p>Any recent release. Confirm it is running before continuing.</p><pre>ollama --version</pre></li><li><b>Download one GGUF file</b><p>Q4_K_M (6.87 GiB) is the recommended default. Use Q6_K (9.11 GiB) on a 16 GiB card if you want the exact build used in the evaluation. Q8_0 is 11.8 GiB and needs more than 16 GiB.</p><pre>hf download Mikrodev/stcoder-gemma4-12b-gguf gemma4_12b-tc.q4_k_m.gguf --local-dir .</pre></li><li><b>Write a Modelfile next to the GGUF</b><p>Deterministic decoding and a short system prompt. For this model a short prompt is the better default: pasting the whole rule book lowered its clean rate from 86.7% to 80.0% and its ChrF from 35.9 to 32.9.</p><pre>cat > Modelfile <<'EOF' | |
| FROM ./gemma4_12b-tc.q4_k_m.gguf | |
| PARAMETER temperature 0 | |
| PARAMETER top_p 1 | |
| PARAMETER num_ctx 8192 | |
| # the published numbers were measured with num_ctx 16384 / num_predict 8192 | |
| # num_predict 2048 is a practical local default (longest reply in the study: 708 tokens); raise it to 8192 to reproduce them or if a long block truncates | |
| PARAMETER num_predict 2048 | |
| SYSTEM """You write IEC 61131-3 Structured Text for the Mikrodev LogicStudio IDE. Reply with the code body only: variable blocks plus logic, no POU wrapper. Timer presets are INT milliseconds. Function blocks are called positionally.""" | |
| EOF</pre></li><li><b>Create the Ollama model</b><p>One-off import. Takes a minute or two depending on disk speed. This is a chat-only model; do not attach tool definitions to it.</p><pre>ollama create stcoder-gemma4-12b:q4 -f Modelfile</pre></li><li><b>Smoke-test it</b><p>You should get a short reply that is mostly one code block: VAR blocks, a TON called positionally, and a millisecond preset. No PROGRAM or FUNCTION_BLOCK line, no T#5s.</p><pre>ollama run stcoder-gemma4-12b:q4 "Grain dryer discharge auger. Start button sounds a siren for 8 seconds, then the auger runs and the siren stops. Stop or any fault drops both immediately."</pre></li><li><b>Reproduce the study numbers (optional)</b><p>The head-to-head in this card used these exact Ollama tags. Fine-tunes: stcoder_qwen25-7b:q8, stcoder_qwen25-14b:q6, stcoder_qwen35-9b:q8, stcoder_gemma4-12b:q6. Bases: qwen2.5-coder:7b, qwen2.5-coder:14b, qwen3.5:9b, gemma4:12b. The base arms' quantisation was NOT recorded in the run data, so the Ollama library default (effectively Q4) is an inference, not a measurement - quantisation was not controlled across the base and fine-tune arms. The study's q6 build is a different file from the q4 default recommended above, so edit the Modelfile's FROM line to the q6 GGUF before running ollama create.</p><pre># the Modelfile written in step 3 points at the q4 file - repoint it before creating the q6 tag | |
| hf download Mikrodev/stcoder-gemma4-12b-gguf gemma4_12b-tc.q6_k.gguf --local-dir . | |
| # edit Modelfile: FROM ./gemma4_12b-tc.q6_k.gguf | |
| ollama create stcoder_gemma4-12b:q6 -f Modelfile | |
| ollama pull gemma4:12b # base arm, library default quant</pre></li><li><b>Compile before you deploy</b><p>Paste the block into LogicStudio and compile it. This is not optional. The rule checker used in the evaluation is reference-free and pattern-based, and in this model's own test rows it passed code that would not compile, including a FOR loop whose index variable was never declared.</p></li></ol> | |
| </section> | |
| <!-- 4. PROMPTS --> | |
| <section> | |
| <div class="sh"><span class="sn">4</span><h2>What to ask it first</h2></div> | |
| <p class="lead">These are ordinary single-purpose requests, the kind this model handles well. | |
| Type them as they are.</p> | |
| <div class="pgrid"><div class="pc"><div class="q">Grain dryer discharge auger with a pre-start siren. When the operator presses start, the siren sounds for 8 seconds, then the auger motor runs and the siren goes quiet. Stop or any fault drops both immediately.</div><div class="e">A short body-only block: VAR_INPUT / VAR_OUTPUT, a TON armed positionally with an 8000 ms constant, and the siren and motor derived from the timer output.</div><div class="n">Fresh scenario, not one of the 15 study prompts. On the study's equivalent timer prompt this model was rule-clean, but its reply used a done-flag that it cleared again on the next scan - check that the run request is properly sealed in.</div></div><div class="pc"><div class="q">Baghouse fan run-on. The dust collector fan follows the hammer mill and keeps running 45 seconds after the mill stops so the ducting clears. Emergency stop cuts both at once. Give me a purge-in-progress flag for the HMI.</div><div class="e">A very short block using one off-delay or on-delay timer with a 45000 ms preset, plus a purge flag derived from fan-on-while-mill-off.</div><div class="n">Fresh scenario, not one of the 15 study prompts. Read the timer arming carefully: on the study's run-on prompt this model drove the timer from NOT bMotorRun rather than from the motor itself.</div></div><div class="pc"><div class="q">Pump-house strobe done in software. While the low-level alarm is active the strobe is lit for 250 ms then dark for 2 seconds, repeating. When the alarm clears the strobe stays off.</div><div class="e">Two millisecond constants (250 and 2000) and a small two-state phase machine or a pair of on-delays.</div><div class="n">Fresh scenario, not one of the 15 study prompts. On the study's blink prompt this model put the trigger input in a plain VAR block and left a placeholder comment about a tick source, so move the input to VAR_INPUT and check the phase timers actually reset.</div></div><div class="pc"><div class="q">Start/stop seal-in for a chlorine dosing pump. The start button must be held for 300 ms before the pump picks up, the thermal overload latches the pump out, and the lockout only clears when stop is pressed with the overload already reset. NC stop button, TRUE means healthy.</div><div class="e">A debounce on-delay with a 300 ms preset, an overload latch, and a seal-in latch feeding the contactor output, plus a separate lockout output.</div><div class="n">Fresh scenario, not one of the 15 study prompts. State the wiring convention as shown: on the study's seal-in prompt this model omitted the separate lockout output and treated the stop button as TRUE-when-pressed even though the prompt said NC.</div></div><div class="pc"><div class="q">Scale a 0-10 V tank level transmitter to metres. 0 counts is 0 V, 32767 counts is 10 V, and the tank is ranged 0-6 m. Clamp the result to 0-6, set a wire-break flag below 160 counts, and force the output to zero when the enable bit is off.</div><div class="e">REAL constants for the raw and engineering endpoints, straight-line arithmetic with no conversion call, and IF-based clamping instead of a LIMIT function.</div><div class="n">Fresh scenario, not one of the 15 study prompts. If you also want a rounded integer for the HMI, say "no conversion functions" explicitly: on the study's scaling-helper prompt this model reached for REAL_TO_INT(pressureBar * 10.0).</div></div><div class="pc"><div class="q">Operators enter a chilled water setpoint on the HMI. Pull anything below 6.0 or above 14.0 degC back to the nearest limit and raise an out-of-range warning the operator can acknowledge. The setpoint sent to the controller must not move more than 0.5 degC per scan.</div><div class="e">An IF/ELSIF clamp with a warning bit, an acknowledge path, and a per-scan ramp using a 0.5 degC step constant.</div><div class="n">Fresh scenario, not one of the 15 study prompts. A good first exercise in getting clamping without MIN/MAX/LIMIT, which the dialect forbids; the study's equivalent clamp prompt was rule-clean at R24 100.</div></div><div class="pc"><div class="q">Motor winding temperature watchdog. Average the last 16 temperature samples, and latch a trip bit if the average stays above 130.0 degC for 5 seconds. Only the reset pushbutton clears it.</div><div class="e">A 1-D ARRAY ring buffer, an averaging loop, and a TON with a 5000 ms preset feeding a latch.</div><div class="n">Fresh scenario, not one of the 15 study prompts, but close in spirit to one of the three contrast examples below. Expect to add the FOR loop index to the VAR block yourself: in the study's averaging prompt the model wrote FOR i := 0 TO 7 DO without ever declaring i.</div></div><div class="pc"><div class="q">Palletiser case counter. A photo eye pulses once for every case that passes. I need a lifetime total for the maintenance record and a shift total that the supervisor clears at handover. Count on the rising edge of the photo eye only.</div><div class="e">Two counters, edge detection on the photo-eye input, and a reset that clears the shift total without touching the lifetime total.</div><div class="n">Fresh scenario, not one of the 15 study prompts. Ask for edge detection in so many words, as above: in the study's counter prompt the model's comment said "on every rising edge" while the code tested the input level, and it also declared lifetimeCount : DINT RETAIN, a retain qualifier the dialect forbids and the pattern checker did not catch.</div></div></div> | |
| <ul><li>Keep the system prompt short. This is the one model in the line where the full rule book hurts: clean drops from 86.7% to 80.0%, ChrF from 35.9 to 32.9, and mean output more than doubles to 921 tokens while the median stays at 354. The rules are in the weights; leave them out of the prompt.</li><li>Run it greedy. temperature 0, top_p 1. Every figure quoted here comes from greedy decoding at seed 42, and sampling will move them.</li><li>Treat it as chat-only. Tool calling was dropped as a product decision for this line and this family measured 0.00 on the tool-call metric, so do not attach tool definitions and do not expect tool syntax back.</li><li>Write the request the way you would describe it to a colleague: the plant, the signals, the timings in seconds, what has to drop out immediately. All 15 study prompts were written that way and none mentioned a dialect rule.</li><li>State your wiring conventions. On the study's seal-in prompt it treated the stop button as TRUE-when-pressed although the prompt said NC, so say "NC stop, TRUE means healthy" if that is your convention.</li><li>If a manual gives you hex masks, convert them yourself or say "decimal constants only, no 16# literals". This is its most repeatable rule failure, and it persisted in both prompt conditions.</li><li>Same for conversions: if the answer needs an integer for the HMI, add "no conversion functions, assign between REAL and INT directly", because this is where it reached for REAL_TO_INT - on two different scaling prompts in the full condition.</li><li>Check the variable blocks before you check the logic. Its replies sometimes put an input in a plain VAR block, omit an output you asked for, use an index variable it never declared, or attach a RETAIN qualifier the dialect forbids.</li><li>Check every timer's arming expression. Rule compliance says nothing about whether the timer resets when you expect. Look for timers driven from their own Q output, and for run requests that are not sealed in.</li><li>For counters, ask explicitly for edge detection on the input signal. Left to itself it tested the level of the photo eye, which increments once per scan.</li><li>If a reply starts repeating declarations or runs much longer than usual, stop it and re-ask with a shorter system prompt. In this study one full-condition reply ran to the 8192-token limit as a wall of repeated BOOL declarations - and still scored R24 100.</li><li>Compile in LogicStudio before anything reaches a panel, and treat R24-clean as a style check only. It is reference-free and pattern-based; it will pass code that does not build.</li><li>If you have not committed to the Gemma lineage, the line's default is stcoder-qwen25-7b at Q6_K (5.82 GiB) and it is the better first download.</li></ul> | |
| </section> | |
| <!-- 5. EVIDENCE --> | |
| <section> | |
| <div class="sh"><span class="sn">5</span><h2>What fine-tuning changed, measured</h2></div> | |
| <p class="lead">The same 15 requests were sent to the stock base model | |
| (<code>gemma4:12b</code>) and to this fine-tune, twice: once with a short realistic | |
| system prompt that lists <b>no dialect rules</b>, and once with the entire rule book pasted in. | |
| Greedy decoding, fixed seed, identical prompts.</p> | |
| <div class="fig"> | |
| <div class="fc">Figure 1 — dialect-clean replies out of 15</div> | |
| <div class="ftl">Clean means: code was delivered and it broke no dialect rule</div> | |
| <div class="legend"><span><i class="sw b"></i>base</span><span><i class="sw f"></i>this fine-tune</span></div> | |
| <div class="frow"><div class="flab">No rules in prompt<small>realistic use</small></div> | |
| <div class="fbars"><div class="bar base"><i style="width:6.7%"></i><b>6.7%</b></div><div class="bar ft"><i style="width:86.7%"></i><b>86.7%</b></div></div></div> | |
| <div class="frow"><div class="flab">Full rule book in prompt<small>charitable to base</small></div> | |
| <div class="fbars"><div class="bar base2"><i style="width:73.3%"></i><b>73.3%</b></div><div class="bar ft2"><i style="width:80.0%"></i><b>80.0%</b></div></div></div> | |
| </div> | |
| <div class="fig"> | |
| <div class="fc">Figure 2 — per-prompt outcome, no rules in prompt</div> | |
| <div class="ftl">One tick per test prompt, same order in both rows</div> | |
| <div class="legend"><span><i class="sw f"></i>clean</span><span><i class="sw b"></i>rule violation</span> | |
| <span><i class="sw r"></i>no code delivered</span></div> | |
| <div class="frow"><div class="flab">Base</div><div class="strip"><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="vi"></i><i class="ok"></i><i class="vi"></i><i class="vi"></i></div></div> | |
| <div class="frow"><div class="flab">Fine-tune</div><div class="strip"><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="vi"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i><i class="vi"></i><i class="ok"></i><i class="ok"></i><i class="ok"></i></div></div> | |
| </div> | |
| <div class="tw"><table> | |
| <thead><tr><th>Measure</th><th>Base</th><th>This fine-tune</th><th>Base + full rules</th><th>Fine-tune + full rules</th></tr></thead> | |
| <tbody> | |
| <tr><td>Dialect-clean replies</td><td class="num bs">6.7%</td><td class="num ft">86.7%</td><td class="num">73.3%</td><td class="num">80.0%</td></tr> | |
| <tr><td>Delivered code at all</td><td class="num">100.0%</td><td class="num">100.0%</td><td class="num">80.0%</td><td class="num">100.0%</td></tr> | |
| <tr><td>R24 rule compliance</td><td class="num">92.55</td><td class="num">99.61</td><td class="num">99.75</td><td class="num">99.41</td></tr> | |
| <tr><td>ChrF vs reference code</td><td class="num">27.7</td><td class="num">35.9</td><td class="num">24.9</td><td class="num">32.9</td></tr> | |
| <tr><td>Output tokens (mean · median)</td><td class="num">2490 · 2411</td><td class="num">401 · 360</td><td class="num">4585 · 4362</td><td class="num">921 · 354</td></tr> | |
| <tr><td>Time per reply</td><td class="num">34.7s</td><td class="num">8.5s</td><td class="num">63.1s</td><td class="num">16.5s</td></tr> | |
| <tr><td>Throughput</td><td class="num">79.5 t/s</td><td class="num">72.7 t/s</td><td class="num">82.8 t/s</td><td class="num">68.4 t/s</td></tr> | |
| <tr><td>Opened with prose, not code</td><td class="num">100.0%</td><td class="num">20.0%</td><td class="num">80.0%</td><td class="num">6.7%</td></tr> | |
| </tbody> | |
| </table></div> | |
| <p class="note"><b>On throughput:</b> the fine-tune is served here at higher precision than the | |
| stock base model (Ollama's default build is more heavily compressed), so it generates more slowly | |
| per token. Quantisation was not matched in this comparison — see the limitations.</p> | |
| </section> | |
| <!-- 6. VIOLATIONS --> | |
| <section> | |
| <div class="sh"><span class="sn">6</span><h2>Which dialect rules the base model breaks</h2></div> | |
| <p class="lead">Counted over replies that actually contained code. The upper bar is the realistic | |
| condition; the lower, lighter bar is with the full rule book in the system prompt — the | |
| difference shows which habits an instruction can fix and which it cannot.</p> | |
| <div class="fig"> | |
| <div class="fc">Figure 3 — base-model rule violations, gemma4:12b — | |
| all 9 rules it broke</div> | |
| <div class="legend" style="margin-bottom:10px"><span style="color:var(--dim)">bar length = share | |
| of delivered replies that broke the rule, full width = 100%</span></div> | |
| <div class="frow"><div class="flab"><code>no_named_param</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:46.7%"></i><b>7/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_TIME_literal</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:46.7%"></i><b>7/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_pou_program</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:40.0%"></i><b>6/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_power_op</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:20.0%"></i><b>3/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_pou_fb</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:20.0%"></i><b>3/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_based_literal</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:6.7%"></i><b>1/15</b></div><div class="bar base2"><i style="width:8.3%"></i><b>1/12</b></div></div></div><div class="frow"><div class="flab"><code>no_cast_function</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:13.3%"></i><b>2/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_output_arrow</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:6.7%"></i><b>1/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div><div class="frow"><div class="flab"><code>no_struct</code><small>minimal · full rules</small></div><div class="fbars"><div class="bar base"><i style="width:6.7%"></i><b>1/15</b></div><div class="bar base2"><i style="width:0.7%"></i><b>0/12</b></div></div></div> | |
| </div> | |
| <p class="note">For comparison, this fine-tune's violations across its 15 delivered | |
| replies in the same condition: <code>no_based_literal</code>×1, <code>no_cast_function</code>×1.</p> | |
| </section> | |
| <!-- 7. CODE --> | |
| <section> | |
| <div class="sh"><span class="sn">7</span><h2>The same request, side by side</h2></div> | |
| <p class="lead">Highlighted tokens are the ones that decide whether the code is accepted by the | |
| IDE. Both columns are verbatim model output.</p> | |
| <div style="margin-top:26px"> | |
| <h3>Reusable pump block: four rules broken at once, then none</h3> | |
| <p class="pmeta" style="margin-bottom:8px">reusable_pump_block · program_structure | |
| · no dialect rules in the system prompt</p> | |
| <p class="note" style="margin-bottom:12px"><b>The request:</b> We have three identical booster pumps on a skid and I am tired of copy-pasting the same logic three times. Give me one reusable pump control block that I can instantiate once per pump. Per instance: run request, motor thermal overload contact, dry-run (low suction) switch, fault reset. Outputs: motor run command, fault flag, and a fault code for the HMI so the operator can see what tripped. Behavi</p> | |
| <div class="diff"> | |
| <div class="panel base"><div class="ph"><span class="tag base">Base · gemma4:12b</span> | |
| <span class="badge warn">R24 85.29 · 5239 tok</span></div> | |
| <pre class="plain">To solve this problem efficiently in Mikrodev LogicStudio, we will create a **Function Block (FB)**. This allows you to define the logic once and then "instantiate" it three times in your main program, each with its own set of inputs and outputs. | |
| ### 1. The Function Block: `FB_PumpControl` | |
| Create a new Function Block named `FB_PumpControl`. This block handles all the timing, fault logic, and state management for a single pump. | |
| **Variables (Declaration):** | |
| ```pascal | |
| <span class="bad">FUNCTION_BLOCK</span> FB_PumpControl | |
| VAR_INPUT | |
| xRunRequest : BOOL; // Request from HMI/PLC to run | |
| xOverload : BOOL; // Physical input from motor overload relay | |
| xDryRun : BOOL; // Input from dry-run sensor | |
| xReset : BOOL; // Reset button for faults | |
| END_VAR | |
| VAR_OUTPUT | |
| xMotorRun : BOOL; // Command to the motor contactor | |
| xFault : BOOL; // Fault status for HMI | |
| iFaultCode : INT; // 0=OK, 1=Overload, 2=Dry Run | |
| END_VAR | |
| VAR | |
| timerStart : TON; // Handles the 2s start delay | |
| timerDryRun : TON; // Tracks the 5s grace period for dry run | |
| timerStopLock : TON; // Handles the 10s "off" timer | |
| END_VAR | |
| ``` | |
| **Implementation (Structured Text):** | |
| ```pascal | |
| (* --- Fault Logic --- *) | |
| (* Overload takes priority. If it exists, code is 1. *) | |
| IF xOverload THEN | |
| iFaultCode := 1; | |
| ELSIF xDryRun AND timerDryRun.Q THEN | |
| (* Dry run only triggers if the motor has been running for > 5s *) | |
| iFaultCode := 2; | |
| ELSE | |
| iFaultCode := 0; | |
| END_IF</pre> | |
| <div class="pv"><code class="v">no_TIME_literal</code><code class="v">no_named_param</code><code class="v">no_output_arrow</code><code class="v">no_pou_fb</code></div></div> | |
| <div class="panel ft"><div class="ph"><span class="tag ft">STCoder fine-tune</span> | |
| <span class="badge ok">R24 100.0 · 793 tok</span></div> | |
| <pre class="plain">Here is the reusable `PumpControl` block. | |
| ```iecst | |
| VAR_INPUT | |
| runReq : BOOL; | |
| motorOver : BOOL; | |
| dryRun : BOOL; | |
| faultReset : BOOL; | |
| END_VAR | |
| VAR_OUTPUT | |
| motorRun : BOOL; | |
| faultFlag : BOOL; | |
| faultCode : INT; | |
| END_VAR | |
| VAR | |
| startDelay : TON; | |
| dryRunDelay : TON; | |
| stopDelay : TON; | |
| latched : BOOL; | |
| faultLatched: BOOL; | |
| END_VAR | |
| VAR CONSTANT | |
| START_MS : INT := 2000; | |
| DRYRUN_MS : INT := 5000; | |
| STOP_MS : INT := 10000; | |
| END_VAR | |
| // 1. Fault detection and latching | |
| IF motorOver OR (motorRun AND dryRun) THEN | |
| faultLatched := TRUE; | |
| faultCode := 0; | |
| IF motorOver THEN faultCode := 1; END_IF; | |
| IF (motorRun AND dryRun) THEN faultCode := 2; END_IF; | |
| END_IF; | |
| // 2. Fault reset | |
| IF faultReset AND NOT motorOver AND NOT (motorRun AND dryRun) THEN | |
| faultLatched := FALSE; | |
| END_IF; | |
| faultFlag := faultLatched; | |
| // 3. Start/Stop logic | |
| IF faultLatched THEN | |
| motorRun := FALSE; | |
| ELSE | |
| // Start delay: 2s after runReq | |
| startDelay(runReq, START_MS); | |
| // Stop delay: 10s after runReq goes FALSE | |
| stopDelay(NOT runReq, STOP_MS); | |
| // Motor run command | |
| IF startDelay.Q AND NOT stopDelay.Q THEN | |
| motorRun := TRUE; | |
| ELSE | |
| motorRun := FALSE; | |
| END_IF; | |
| END_IF; | |
| // 4. Dry-run evaluation: only 5s after motorRun is TRUE | |
| IF motorRun THEN | |
| dryRunDelay(TRUE, DRYRUN_MS); | |
| IF dryRunDelay.Q AND dryRun THEN | |
| faultLatched := TRUE; | |
| faultCode := 2; | |
| END_IF; | |
| ELSE | |
| dryR</pre> | |
| <div class="pv"><code class="c">no violations</code></div></div> | |
| </div> | |
| <p class="note">Minimal system prompt, same prompt and same seed for both arms; quantisation was not controlled, so the base ran the Ollama library default while the fine-tune ran Q6_K. The base produced the general-assistant answer you would expect and broke four rules in one file. It opened with FUNCTION_BLOCK FB_PumpControl, which the IDE does not accept because the IDE owns the POU shell and the model should emit the body only. It then called its timers with named parameters and TIME literals: timerDryRun(IN := xMotorRun, PT := T#5s) and timerStopLock(IN := NOT xMotorRun, PT := T#10s), with timerStart(IN := TRUE, PT := T#2s) later in the same reply. R24 85.29, the joint-lowest of its fifteen base rows, and 5239 tokens. The fine-tune dropped the wrapper, declared START_MS / DRYRUN_MS / STOP_MS as INT millisecond constants (2000, 5000, 10000) and called each timer positionally - startDelay(runReq, START_MS). It instantiated the three pumps positionally too, p1(runReq1, motorOver1, dryRun1, faultReset1, motorRun1, faultFlag1, faultCode1), with no => arrows anywhere. R24 100, ChrF 34.4, 793 tokens. Be straight about the logic, though: the minimum-off requirement is not actually enforced. It armed stopDelay(NOT runReq, STOP_MS) and then gated the motor on NOT stopDelay.Q, which does not hold the pump out for 10 seconds before a restart. The dialect is right; the sequence needs your review.</p> | |
| <p class="note" style="color:var(--dim);font-size:12.3px">Red spans in the base panel are the | |
| exact substrings the rule checker matched. The fine-tune panel is shown unmarked because it | |
| broke no rule on this prompt — read it against the base, line for line.</p> | |
| </div> | |
| <div style="margin-top:26px"> | |
| <h3>Drilling station sequencer: three timer calls, three violations</h3> | |
| <p class="pmeta" style="margin-bottom:8px">machine_cycle_full_program · program_structure | |
| · no dialect rules in the system prompt</p> | |
| <p class="note" style="margin-bottom:12px"><b>The request:</b> Can you write the complete program for our drilling station cycle? One part per cycle: when the guard is closed, a part is present and there are no alarms, pressing Start closes the clamp, waits 1.5 s for clamp pressure to build, then the drill motor starts and the head feeds down until the down limit switch, dwells 3 s at depth, retracts until the up limit, opens the clamp after 0.8 s, and the st</p> | |
| <div class="diff"> | |
| <div class="panel base"><div class="ph"><span class="tag base">Base · gemma4:12b</span> | |
| <span class="badge warn">R24 91.18 · 3024 tok</span></div> | |
| <pre class="plain">To implement this in Mikrodev LogicStudio, I have structured the program using a **State Machine** (using a `CASE` statement). This is the industry-standard way to handle sequential processes like drilling cycles because it ensures that only one step happens at a time and makes debugging much easier. | |
| Here is the complete Structured Text (ST) code. | |
| ### Variable Declarations | |
| You should define these variables in your Global Variable List (GVL) or the local variable area of your program: | |
| ```pascal | |
| VAR | |
| (* Inputs *) | |
| i_GuardClosed : BOOL; // Physical sensor for safety gate | |
| i_PartPresent : BOOL; // Sensor detecting a part | |
| i_StartBtn : BOOL; // Operator Start button (Momentary) | |
| i_DownLimit : BOOL; // Limit switch at bottom of stroke | |
| i_UpLimit : BOOL; // Limit switch at top of stroke | |
| i_EStop : BOOL; // E-Stop Button (Normally Closed - Logic High = OK) | |
| i_Overload : BOOL; // Drill Motor Overload sensor | |
| i_Reset : BOOL; // Manual Reset button | |
| (* Outputs *) | |
| q_Clamp : BOOL; // Solenoid to close/hold clamp | |
| q_DrillMotor : BOOL; // Main drill motor | |
| q_FeedDown : BOOL; // Actuator/Motor for downward movement | |
| q_StatusLamp : BOOL; // Indicator light | |
| (* Internal Logic *) | |
| e_State : INT := 0; // Current step in the cycle | |
| e_PartCount : INT; // Counter for finished parts | |
| (* Timers *) | |
| </pre> | |
| <div class="pv"><code class="v">no_TIME_literal</code><code class="v">no_named_param</code></div></div> | |
| <div class="panel ft"><div class="ph"><span class="tag ft">STCoder fine-tune</span> | |
| <span class="badge ok">R24 100.0 · 629 tok</span></div> | |
| <pre class="plain">```iecst | |
| VAR_INPUT | |
| bStart : BOOL; | |
| bGuard : BOOL; | |
| bPartPresent: BOOL; | |
| bDownLimit : BOOL; | |
| bUpLimit : BOOL; | |
| bDrillOver : BOOL; | |
| bReset : BOOL; | |
| bEStop : BOOL; | |
| END_VAR | |
| VAR_OUTPUT | |
| bClamp : BOOL; | |
| bDrill : BOOL; | |
| bFeedDown : BOOL; | |
| bFeedUp : BOOL; | |
| bFault : BOOL; | |
| nPartCount : INT; | |
| END_VAR | |
| VAR | |
| nStep : INT; | |
| tClamp : TON; | |
| tDrill : TON; | |
| tDepth : TON; | |
| tClampOpen : TON; | |
| END_VAR | |
| IF bEStop OR bDrillOver THEN | |
| bClamp := FALSE; | |
| bDrill := FALSE; | |
| bFeedDown := FALSE; | |
| bFeedUp := FALSE; | |
| bFault := TRUE; | |
| IF bReset THEN | |
| bFault := FALSE; | |
| nStep := 0; | |
| END_IF; | |
| ELSE | |
| bFault := FALSE; | |
| CASE nStep OF | |
| 0: // Idle - wait for start | |
| IF bStart AND bGuard AND bPartPresent THEN | |
| bClamp := TRUE; | |
| nStep := 1; | |
| END_IF; | |
| 1: // Clamp pressure build (1.5 s) | |
| tClamp(NOT tClamp.Q, 1500); | |
| IF tClamp.Q THEN | |
| bDrill := TRUE; | |
| bFeedDown := TRUE; | |
| nStep := 2; | |
| END_IF; | |
| 2: // Feed down until down limit | |
| IF bDownLimit THEN | |
| bFeedDown := FALSE; | |
| nStep := 3; | |
| END_IF; | |
| 3: // Dwell at depth (3 s) | |
| tDrill(NOT tDrill.Q, 3000); | |
| IF tDrill.Q THEN | |
| bFeedDown := FALSE; | |
| bFeedUp := TRUE; | |
| nStep := 4; | |
| END_IF; | |
| 4: // Retract until up limit | |
| IF bUpLimit THEN | |
| bFeedUp := FALSE; | |
| nStep := 5; | |
| END_IF; | |
| 5: // Clamp open (0.8 s) | |
| bClamp := FALSE; | |
| tCla</pre> | |
| <div class="pv"><code class="c">no violations</code></div></div> | |
| </div> | |
| <p class="note">Minimal system prompt. Both models chose a CASE step sequencer, so the difference is in packaging and in how the timers are written. The base wrapped its answer in tutorial prose with headings such as "### Variable Declarations", declared the three delays as bare TON instances (timer_Pressure : TON;) and then, further down the reply than the excerpt shown here, called them as timer_Pressure(IN := TRUE, PT := T#1.5s), timer_Dwell(IN := TRUE, PT := T#3.0s) and timer_Release(IN := TRUE, PT := T#0.8s) - named parameters and TIME literals in every one, two rules broken three times each, R24 91.18, ChrF 15.5, 3024 tokens. It also inverted the overload sense in IF NOT i_EStop OR NOT i_Overload THEN, which would fault the station whenever the overload contact was healthy. The fine-tune wrote the same three delays as positional calls with integer millisecond presets: tClamp(NOT tClamp.Q, 1500), tDrill(NOT tDrill.Q, 3000), tClampOpen(NOT tClampOpen.Q, 800). R24 100, ChrF 41.6, 629 tokens, no prose. The dialect is correct throughout. The arming expression is not: driving a timer from NOT its own output makes it re-arm the scan after Q goes true, so Q pulses rather than latching, and the drill motor is never de-energised on retract. Compare it against the reference sequencer, which arms each timer from the active step number instead.</p> | |
| <p class="note" style="color:var(--dim);font-size:12.3px">Red spans in the base panel are the | |
| exact substrings the rule checker matched. The fine-tune panel is shown unmarked because it | |
| broke no rule on this prompt — read it against the base, line for line.</p> | |
| </div> | |
| <div style="margin-top:26px"> | |
| <h3>Heater overload: the POU wrapper and the TIME literal both go</h3> | |
| <p class="pmeta" style="margin-bottom:8px">heater_power_avg_squared · analog_math | |
| · no dialect rules in the system prompt</p> | |
| <p class="note" style="margin-bottom:12px"><b>The request:</b> Heater panel job. I sample the load current every scan and it is noisy, so I need the average of the last 8 samples first. From that averaged current I want the dissipated power using P = I squared times R, with R = 4.7 ohm. If the power stays above 1500 W for 3 seconds, latch an overload bit that only clears when the operator hits the reset pushbutton. Please write it in Structured Text.</p> | |
| <div class="diff"> | |
| <div class="panel base"><div class="ph"><span class="tag base">Base · gemma4:12b</span> | |
| <span class="badge warn">R24 85.29 · 2008 tok</span></div> | |
| <pre class="plain">To implement this logic in Mikrodev LogicStudio, we will use an array to handle the rolling average of the current samples, a standard calculation for power, and a `TON` (Timer On Delay) function block to handle the 3-second duration requirement. | |
| Here is the Structured Text code: | |
| ```pascal | |
| <span class="bad">PROGRAM Heater_Control</span> | |
| VAR | |
| (* Inputs *) | |
| I_raw : REAL; // Current sampled from sensor (Amps) | |
| Reset_PB : BOOL; // Physical Reset Pushbutton | |
| (* Internal Processing *) | |
| I_Buffer : ARRAY[0..7] OF REAL; // Buffer for last 8 samples | |
| i_Idx : INT := 0; // Index for the circular buffer | |
| I_Sum : REAL; // Sum of buffer values | |
| I_Avg : REAL; // Averaged current | |
| (* Constants & Calculations *) | |
| R_Value : REAL := 4.7; // Resistance in Ohms | |
| Power : REAL; // Calculated Power (Watts) | |
| (* Outputs *) | |
| Overload : BOOL; // Latch bit for over-power condition | |
| (* Timer *) | |
| Timer_Overload : TON; // Timer to check 3s duration | |
| END_VAR | |
| *** | |
| (* 1. Rolling Average Logic *) | |
| (* Store the current sample in the buffer and increment index *) | |
| I_Buffer[i_Idx] := I_raw; | |
| i_Idx := (i_Idx + 1) MOD 8; | |
| (* Calculate the sum of all samples in the buffer *) | |
| I_Sum := 0.0; | |
| FOR i := 0 TO 7 DO | |
| I_Sum := I_Sum + I_Buffer[i]; | |
| END_FOR; | |
| (* Calculate average current *) | |
| I_Avg := I_Sum / 8.0; | |
| (* 2. Power Calculation *) | |
| (* P = I^2 * R *) | |
| Pow</pre> | |
| <div class="pv"><code class="v">no_TIME_literal</code><code class="v">no_named_param</code><code class="v">no_pou_program</code><code class="v">no_power_op</code></div></div> | |
| <div class="panel ft"><div class="ph"><span class="tag ft">STCoder fine-tune</span> | |
| <span class="badge ok">R24 100.0 · 327 tok</span></div> | |
| <pre class="plain">```iecst | |
| VAR_INPUT | |
| rCurrentSample : REAL; | |
| bResetBtn : BOOL; | |
| END_VAR | |
| VAR_OUTPUT | |
| bOverload : BOOL; | |
| END_VAR | |
| VAR | |
| rSamples : ARRAY[0..7] OF REAL; | |
| rAvgCurrent : REAL; | |
| rPower : REAL; | |
| rR : REAL := 4.7; | |
| rOverloadTimer : TON; | |
| rSampleIdx : INT := 0; | |
| END_VAR | |
| (* 8-sample moving average *) | |
| rSamples[rSampleIdx] := rCurrentSample; | |
| rSampleIdx := rSampleIdx + 1; | |
| IF rSampleIdx >= 8 THEN rSampleIdx := 0; END_IF; | |
| rAvgCurrent := 0.0; | |
| FOR i := 0 TO 7 DO rAvgCurrent := rAvgCurrent + rSamples[i]; END_FOR; | |
| rAvgCurrent := rAvgCurrent / 8.0; | |
| (* Power calculation *) | |
| rPower := rAvgCurrent * rAvgCurrent * rR; | |
| (* Overload detection: 3 seconds above 1500 W *) | |
| rOverloadTimer(rPower > 1500.0, 3000); | |
| (* Latch/Reset logic *) | |
| IF rOverloadTimer.Q THEN | |
| bOverload := TRUE; | |
| END_IF; | |
| IF bResetBtn THEN | |
| bOverload := FALSE; | |
| END_IF; | |
| ```</pre> | |
| <div class="pv"><code class="c">no violations</code></div></div> | |
| </div> | |
| <p class="note">Minimal system prompt. The base opened with PROGRAM Heater_Control and declared everything inside it, which the IDE rejects outright, then timed the overload with Timer_Overload(IN := (Power > 1500.0), PT := T#3s); - a named parameter and a TIME literal in one line - and reached for ** in its explanatory text. Four rules broken, R24 85.29, ChrF 36.2, 2008 tokens. The fine-tune emitted the body only, split the signals into VAR_INPUT and VAR_OUTPUT, used ARRAY[0..7] OF REAL for the eight-sample buffer, squared the current by multiplying it with itself (rAvgCurrent * rAvgCurrent * rR, no ** operator) and timed the overload positionally as rOverloadTimer(rPower > 1500.0, 3000). R24 100 against 85.29, ChrF 42.3 against 36.2, 327 tokens against 2008. This is also the clearest illustration of what R24 does not measure. Both replies wrote FOR i := 0 TO 7 DO without ever declaring i, so neither would compile as delivered, and the fine-tune exposed only bOverload as an output while keeping the averaged current and the calculated power internal, where the reference publishes all three. Add the loop index and promote the two values you want on the HMI, then compile.</p> | |
| <p class="note" style="color:var(--dim);font-size:12.3px">Red spans in the base panel are the | |
| exact substrings the rule checker matched. The fine-tune panel is shown unmarked because it | |
| broke no rule on this prompt — read it against the base, line for line.</p> | |
| </div> | |
| </section> | |
| <!-- 8. FULL RESULTS --> | |
| <section> | |
| <div class="sh"><span class="sn">8</span><h2>Every test prompt and its result</h2></div> | |
| <p class="lead">All 15 prompts, realistic condition (no rules in the system prompt). Prompt text | |
| is in the appendix below.</p> | |
| <div class="tw"><table> | |
| <thead><tr><th>Prompt</th><th>Base</th><th>R24</th><th>ChrF</th><th>tok</th> | |
| <th>Fine-tune</th><th>R24</th><th>ChrF</th><th>tok</th><th style="text-align:left">Rules the base broke</th></tr></thead> | |
| <tbody><tr><td><code>analog_scale_raw_to_bar</code><br><span style="color:var(--dim);font-size:11.5px">analog_math</span></td><td><span class="chip vi">violation</span></td><td class="num bs">94.12</td><td class="num">35.02</td><td class="num">1959</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">42.66</td><td class="num">360</td><td style="text-align:left;font-size:11.6px"><code>no_cast_function</code>, <code>no_pou_program</code></td></tr><tr><td><code>setpoint_clamp_and_ramp</code><br><span style="color:var(--dim);font-size:11.5px">analog_math</span></td><td><span class="chip vi">violation</span></td><td class="num bs">97.06</td><td class="num">40.71</td><td class="num">2511</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">39.27</td><td class="num">306</td><td style="text-align:left;font-size:11.6px"><code>no_pou_program</code></td></tr><tr><td><code>heater_power_avg_squared</code><br><span style="color:var(--dim);font-size:11.5px">analog_math</span></td><td><span class="chip vi">violation</span></td><td class="num bs">85.29</td><td class="num">36.18</td><td class="num">2008</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">42.32</td><td class="num">327</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code>, <code>no_pou_program</code>, <code>no_power_op</code></td></tr><tr><td><code>timer_start_delay</code><br><span style="color:var(--dim);font-size:11.5px">timer_time_based</span></td><td><span class="chip vi">violation</span></td><td class="num bs">91.18</td><td class="num">23.23</td><td class="num">2551</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">44.61</td><td class="num">307</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code></td></tr><tr><td><code>timer_blink_oscillator</code><br><span style="color:var(--dim);font-size:11.5px">timer_time_based</span></td><td><span class="chip vi">violation</span></td><td class="num bs">91.18</td><td class="num">27.99</td><td class="num">1811</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">34.53</td><td class="num">414</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code></td></tr><tr><td><code>timer_offdelay_coastdown</code><br><span style="color:var(--dim);font-size:11.5px">timer_time_based</span></td><td><span class="chip vi">violation</span></td><td class="num bs">91.18</td><td class="num">19.96</td><td class="num">2857</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">33.24</td><td class="num">180</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code></td></tr><tr><td><code>io_terminal_seal_in</code><br><span style="color:var(--dim);font-size:11.5px">io_misc</span></td><td><span class="chip vi">violation</span></td><td class="num bs">88.24</td><td class="num">33.63</td><td class="num">2820</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">42.55</td><td class="num">399</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code>, <code>no_pou_program</code></td></tr><tr><td><code>status_word_bit_mask</code><br><span style="color:var(--dim);font-size:11.5px">io_misc</span></td><td><span class="chip vi">violation</span></td><td class="num bs">94.12</td><td class="num">40.25</td><td class="num">1695</td><td><span class="chip vi">violation</span></td><td class="num ft">97.06</td><td class="num">38.85</td><td class="num">351</td><td style="text-align:left;font-size:11.6px"><code>no_based_literal</code>, <code>no_pou_program</code></td></tr><tr><td><code>first_out_fault_annunciator</code><br><span style="color:var(--dim);font-size:11.5px">io_misc</span></td><td><span class="chip vi">violation</span></td><td class="num bs">94.12</td><td class="num">40.23</td><td class="num">2874</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">43.69</td><td class="num">599</td><td style="text-align:left;font-size:11.6px"><code>no_pou_program</code>, <code>no_power_op</code></td></tr><tr><td><code>machine_cycle_full_program</code><br><span style="color:var(--dim);font-size:11.5px">program_structure</span></td><td><span class="chip vi">violation</span></td><td class="num bs">91.18</td><td class="num">15.47</td><td class="num">3024</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">41.56</td><td class="num">629</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code></td></tr><tr><td><code>reusable_pump_block</code><br><span style="color:var(--dim);font-size:11.5px">program_structure</span></td><td><span class="chip vi">violation</span></td><td class="num bs">85.29</td><td class="num">15.09</td><td class="num">5239</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">34.44</td><td class="num">793</td><td style="text-align:left;font-size:11.6px"><code>no_TIME_literal</code>, <code>no_named_param</code>, <code>no_output_arrow</code>, <code>no_pou_fb</code></td></tr><tr><td><code>analog_scale_helper</code><br><span style="color:var(--dim);font-size:11.5px">program_structure</span></td><td><span class="chip vi">violation</span></td><td class="num bs">94.12</td><td class="num">33.11</td><td class="num">2184</td><td><span class="chip vi">violation</span></td><td class="num ft">97.06</td><td class="num">25.55</td><td class="num">385</td><td style="text-align:left;font-size:11.6px"><code>no_cast_function</code>, <code>no_pou_fb</code></td></tr><tr><td><code>retain_counter_power_cycle</code><br><span style="color:var(--dim);font-size:11.5px">data_memory</span></td><td><span class="chip ok">clean</span></td><td class="num bs">100.0</td><td class="num">10.39</td><td class="num">1670</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">19.81</td><td class="num">226</td><td style="text-align:left;font-size:11.6px">—</td></tr><tr><td><code>shared_project_value</code><br><span style="color:var(--dim);font-size:11.5px">data_memory</span></td><td><span class="chip vi">violation</span></td><td class="num bs">97.06</td><td class="num">37.0</td><td class="num">1734</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">31.78</td><td class="num">278</td><td style="text-align:left;font-size:11.6px"><code>no_pou_fb</code></td></tr><tr><td><code>machine_data_grouping</code><br><span style="color:var(--dim);font-size:11.5px">data_memory</span></td><td><span class="chip vi">violation</span></td><td class="num bs">94.12</td><td class="num">6.98</td><td class="num">2411</td><td><span class="chip ok">clean</span></td><td class="num ft">100.0</td><td class="num">23.09</td><td class="num">455</td><td style="text-align:left;font-size:11.6px"><code>no_power_op</code>, <code>no_struct</code></td></tr></tbody> | |
| </table></div> | |
| </section> | |
| <!-- 9. QUANT --> | |
| <section> | |
| <div class="sh"><span class="sn">9</span><h2>Q4, Q6 and Q8 — what actually differs</h2></div> | |
| <p>Quantisation reduces the precision of the stored weights: Q8_0 is closest to the trained values, Q6_K is a middle ground, Q4_K_M is the smallest of the three published builds. All file sizes in this card are GiB as reported by the build tooling. | |
| For this model the practical spread is narrow. The earlier internal evaluation, which ran 2000 prompts across all three quants of these same fine-tunes, put the Gemma code composite between 0.800 and 0.810 and ChrF between 38 and 41 across Q4, Q6 and Q8. Dropping from Q8_0 (11.8 GiB) to Q4_K_M (6.87 GiB) therefore costs roughly one hundredth of a composite point on the code task while saving nearly 5 GiB of VRAM. | |
| That evaluation has to be read with one caveat, and it is a large one: its code holdout overlapped the training distribution by about 84%, so the absolute code scores are inflated and weakly discriminative between models. They are usable for the within-model quant comparison above - all three quants were scored on the same contaminated holdout, so the comparison between them is still informative - and they must not be used to rank stcoder-gemma4-12b against its siblings. For cross-model figures, use the head-to-head table in this card. | |
| One correction worth stating plainly, because an earlier version of the same report showed a much larger Q4 penalty. That penalty was measured on the tool-calling task, not on code, and it belonged to the Qwen3.5-9B family (tool-call composite 0.805 at Q6_K falling to 0.642 at Q4_K_M). This Gemma family scored exactly 0.00 on tool calling at every quant, and tool calling is not part of this release in any case. Structured Text generation is comparatively tolerant of low precision here. | |
| Two build-specific notes. The head-to-head study in this card was run at Q6_K, so those clean rates and ChrF figures describe that build. And Q8_0 is 11.8 GiB and has crashed on a 16 GiB card in internal testing, so the practical ceiling on a 16 GiB workstation is Q6_K. Q4_K_M is the recommended default.</p> | |
| <div class="card"><h3>Evidence base — and its limit</h3> | |
| <p>From an earlier 2,000-prompt internal evaluation of these same fine-tunes: | |
| <b>code composite 0.800-0.810 and ChrF 38-41 across Q4/Q6/Q8</b>. The head-to-head study on this page used the | |
| <b>Q6_K</b> build. File sizes are <b>GiB</b>.</p> | |
| <p class="note" style="margin-top:10px"><b>Important caveat.</b> That earlier evaluation's code | |
| holdout overlapped the training distribution by roughly 84%, so its absolute code scores are | |
| inflated and only weakly separate one model from another. Use those figures for the | |
| <i>within-model</i> quantisation comparison, which is what they are cited for here — not | |
| to rank the four models against each other. For cross-model ranking use the identical-conditions | |
| table in section 2.</p></div> | |
| </section> | |
| <!-- 10. STRENGTHS --> | |
| <section> | |
| <div class="sh"><span class="sn">10</span><h2>Strengths, weaknesses, and what not to use it for</h2></div> | |
| <h3>Strengths</h3><ul><li>Under a minimal system prompt the fine-tune lifts dialect compliance from 6.7% clean (1 of 15) to 86.7% clean (13 of 15) and mean R24 from 92.55 to 99.61, with no rules shown to the model. These are whole-reply figures; on fence-only scoring the base rises to 20.0% (3 of 15) and mean R24 94.71 while the fine-tune is unchanged at 86.7% and 99.61. Quantisation was not controlled: the fine-tune ran Q6_K and the stock base ran the Ollama library default, which is an inference of effectively Q4 rather than a recorded value.</li><li>The habits that break the base model most often disappear. Across the 15 minimal-condition prompts the base broke TIME literals 7 times, named function-block parameters 7 times and POU wrappers 9 times (no_pou_program 6 plus no_pou_fb 3); the fine-tune broke none of the three, leaving one hex literal and one cast function in total. On fence-only scoring the base's TIME-literal and named-parameter counts fall to 4 each, while the 9 POU wrappers stand.</li><li>It always answers. Delivery is 100% (15 of 15) in both fine-tuned arms, and under the full rule book the stock base returned code for only 12 of 15 prompts while the fine-tune returned 15 of 15.</li><li>Output is short and paste-ready. Mean 401 tokens and median 360 against the base's 2490 mean and 2411 median, 8.5 s against 34.7 s per reply on an RTX 5080, no reasoning block at all (0% against the base's 100%), and prose around the code in 20% of replies against the base's 100% (6.7% under the full prompt).</li><li>Different lineage from the other three STCoder models, which makes it usable as an independent second opinion on an ambiguous request rather than a correlated one.</li><li>It respects some rules it was never shown. On the first-out annunciator prompt it avoided STRING types, single-quoted literals and string functions - all forbidden by the dialect - without being told, scoring R24 100 and ChrF 43.7, its highest ChrF outside the timer prompts.</li><li>Quantisation is forgiving on this task: the earlier 2000-prompt evaluation put Q4, Q6 and Q8 of this fine-tune inside a code composite band of 0.800-0.810, so the 6.87 GiB Q4_K_M build is a reasonable default rather than a compromise. That evaluation's code holdout overlapped the training distribution by about 84%, so read the band as a within-model comparison only, never as a cross-model result.</li></ul> | |
| <h3>Weaknesses</h3><ul><li>Weakest fine-tune clean rate and lowest composite of the four models: 86.7% clean (13 of 15) under a minimal prompt and 80.0% (12 of 15) under the full rule book, against 93.3% for stcoder-qwen25-7b, 100.0% for stcoder-qwen25-14b and 93.3% for stcoder-qwen35-9b; composite 0.830 against 0.844, 0.842 and 0.845. Whole-reply scoring in every case.</li><li>The fine-tuning case is weakest here, and in one respect the base wins. Given the whole rule book in the system prompt, the stock base is 73.3% clean (11 of 15) against this fine-tune's 80.0% (12 of 15), and the base's mean R24 in that condition is higher than the fine-tune's: 99.80 pooled over all 15 rows, 99.75 over its 12 delivered replies, against the fine-tune's 99.41. The fine-tune still wins that condition on delivery (15 of 15 against 12 of 15) and ChrF (32.9 against 24.9 over delivered replies), and quantisation was not controlled between the two arms - but on this lineage a long rule preamble gets a stock Gemma most of the way there.</li><li>Similarity to house style is mid-pack in this study and last in the earlier one, and the two must not be spliced. In this head-to-head its ChrF is 35.9 over 15 of 15 delivered, above stcoder-qwen25-14b (34.9) and below stcoder-qwen25-7b (38.5, also 15 of 15) and stcoder-qwen35-9b (40.9, but over only 14 of 15). In the earlier 2000-prompt evaluation it is the lowest of the four (38-41 against 47-52 for the Qwen-based siblings) - and that evaluation's code holdout overlapped the training distribution by about 84%, so those absolute scores are inflated and weakly discriminative between models and should not be used to rank them.</li><li>Adding the rule book makes it worse, not better: clean falls from 86.7% to 80.0%, ChrF from 35.9 to 32.9, and mean output more than doubles from 401 to 921 tokens while the median stays at 354. One reply is responsible for the whole tail, and it is in THIS study, not earlier testing: full condition, first_out_fault_annunciator, 8192 output tokens, truncated mid-declaration, a wall of bResetDone_2 through bResetDone_673 BOOL declarations. It scored R24 100.0 with no violations, so it is counted INSIDE the 80.0% clean figure - read that figure with this caveat. Its ChrF on that row was 6.29.</li><li>Two rules it still breaks. It keeps hex literals when a manual gives them that way: the VFD status-word prompt produced bReady := (nStatusWord AND 16#0001) <> 0; in both conditions. And it reaches for cast functions in scaling work: the two no_cast_function rows in the full condition are two DIFFERENT prompts, analog_scale_raw_to_bar writing rBar := (REAL(iRawCount) - COUNT_4MA) / (COUNT_20MA - COUNT_4MA) * RANGE_BAR; and analog_scale_helper writing pressureBar := INT(scaled * 10.0); - the same helper prompt without the rule book reached for pressureTenths := REAL_TO_INT(pressureBar * 10.0); so the habit survives both prompt forms.</li><li>The Q8_0 build (11.8 GiB) has crashed on a 16 GiB card in internal testing. Treat Q6_K as the practical ceiling on 16 GiB.</li><li>Rule-clean is not compile-clean, and this model shows it plainly. Its heater reply scored R24 100 while writing FOR i := 0 TO 7 DO with no declaration of i. Its multi-machine reply scored R24 100 with declarations written as tempSet[3] : REAL;, which is not valid array syntax. Its counter reply scored R24 100 while declaring lifetimeCount : DINT RETAIN;, a retain qualifier the dialect forbids and the pattern checker did not catch.</li><li>Logic errors survive rule compliance. The same counter reply comments "increment lifetime total on every rising edge of the photo eye" and then writes IF bPartDetect THEN - the level, not the edge - so it will count once per scan for as long as a case sits in front of the sensor.</li></ul> | |
| <h3>Not the right tool for</h3><ul><li>Tool calling or function calling of any kind. Tool calling was trained and evaluated for this line, but it was not usable across the line and was dropped as a product decision, so these builds are published and supported as chat-only models. Measured tool-call composite: 0.816 for the Qwen3.5-9B family at Q8_0 (0.805 at Q6_K, 0.642 at Q4_K_M), 0.02-0.06 for the code-focused Qwen2.5-Coder builds, and exactly 0.00 for this Gemma family. If you need an agent that drives IDE tools, do not use these models.</li><li>Anyone who will not compile the output before deployment. In this model's own test rows, R24-clean code included an undeclared loop variable, an invalid array declaration and a forbidden RETAIN qualifier.</li><li>Modbus or fieldbus status-word decoding where the manual quotes hex masks. This is its most repeatable rule failure: it keeps 16#0001 style literals rather than converting them to decimal constants.</li><li>Undecided readers, and anyone choosing on measured quality alone. Take the line's default, stcoder-qwen25-7b at Q6_K (5.82 GiB): in this head-to-head it leads on composite (0.844) and on ChrF at full delivery (38.5 over 15 of 15), and it is the fastest per answer at 5.8 s. If you want the one model that was 100% rule-clean in both conditions, take stcoder-qwen25-14b at Q4_K_M (8.37 GiB), accepting the lowest head-to-head ChrF (34.9) and the slowest generation per token (about 50 tok/s).</li><li>8 GiB cards. Q4_K_M weights alone are 6.87 GiB, leaving no useful context budget; use stcoder-qwen25-7b at Q6_K (5.82 GiB) instead.</li><li>16 GiB cards at Q8_0. That build is 11.8 GiB and has crashed on a 16 GiB card.</li><li>Frontier-model work: multi-file architecture, long-horizon reasoning, or anything requiring the model to hold a whole project in mind. This is a 12B model built to run on a workstation.</li></ul> | |
| </section> | |
| <!-- 11. METHOD --> | |
| <section> | |
| <div class="sh"><span class="sn">11</span><h2>How the numbers were produced</h2></div> | |
| <p>240 controlled generations across four model families, 15 prompts each, run twice per model | |
| under two prompting conditions. Greedy decoding (<code>temperature 0</code>, <code>seed 42</code>), | |
| 8,192-token budget, local Ollama on an RTX 5080 (16 GB).</p> | |
| <ul> | |
| <li><b>R24</b> — dialect rule compliance. 33 forbidden patterns, individually weighted; the | |
| score is the share of total weight left intact (0–100). Comments, prose and string contents | |
| are removed before matching, so discussion never triggers a rule.</li> | |
| <li><b>ChrF</b> — character n-gram F-score of the generated code against a hand-written | |
| reference solution (sacreBLEU chrF++). Higher means closer to house style. An alternative but | |
| correct solution legitimately scores lower, so it is always read together with R24.</li> | |
| <li><b>Clean</b> — the reply delivered code <i>and</i> broke no rule. A reply containing no | |
| code scores R24 = 100 trivially, because there is nothing left to violate, so delivery is | |
| checked first.</li> | |
| <li><b>Which R24 variant.</b> The R24 and clean figures on this page are computed over the | |
| <b>whole reply</b>. That matters because base replies were prose-wrapped 100.0% of the | |
| time for this model: scoring only the fenced code lifts the pooled base clean rate across the | |
| study from 6.7% to 10.0%. The base-versus-fine-tune gap is not sensitive to the choice, but the | |
| variant is named here so the numbers can be reproduced exactly.</li> | |
| <li><b>Test prompts</b> — written as ordinary plant requests. No prompt mentions any dialect | |
| rule; a prompt that said “use milliseconds” would hand the answer to the base model. | |
| Reference solutions were verified rule-clean before use.</li> | |
| </ul> | |
| <div class="card warn"><h3>Limitations</h3> | |
| <ul> | |
| <li><b>Quantisation was not matched.</b> The fine-tune ran at higher precision than the stock | |
| base build. This plausibly helps the fine-tune on quality and demonstrably penalises it on | |
| raw throughput.</li> | |
| <li><b>“Clean” is not “correct”.</b> R24 is reference-free: it certifies | |
| that no forbidden construct appears, not that the logic works. Correctness was checked by | |
| reading the code, not by compiling it.</li> | |
| <li><b>The published builds do not ship this decoding configuration.</b> These numbers were | |
| measured greedily (<code>temperature 0</code>, | |
| <code>seed 42</code>) at | |
| <code>num_ctx 16384</code> / | |
| <code>num_predict 8192</code>. The Modelfiles published with | |
| the models default to <code>temperature 0.2</code> and <code>num_ctx 8192</code> — the | |
| sequence length the models were trained at — because that is the better interactive | |
| default. Match the three measured values to reproduce these figures; otherwise expect | |
| run-to-run variation.</li> | |
| <li><b>Scope.</b> 15 prompts, one dialect, English only, no tool-calling, and the prompt set | |
| was deliberately built on the categories where this dialect diverges from standard IEC. It | |
| measures dialect fidelity on dialect-sensitive work, not general PLC competence.</li> | |
| <li><b>Small sample.</b> 15 prompts per condition per model. Treat single-prompt differences | |
| as anecdote and the aggregate as the signal.</li> | |
| </ul> | |
| </div> | |
| </section> | |
| <!-- 12. CLOSING --> | |
| <section> | |
| <div class="sh"><span class="sn">12</span><h2>In summary</h2></div> | |
| <p>On a short, realistic system prompt this fine-tune turns a stock Gemma-4-12B from 6.7% dialect-clean into 86.7% dialect-clean (1 of 15 to 13 of 15), removes TIME literals, named parameters and POU wrappers entirely, and does it in 401 tokens and 8.5 seconds instead of 2490 tokens and 34.7 seconds. Three caveats travel with that claim, and they apply to every card in this set. Quantisation was not controlled: the fine-tune ran Q6_K while the stock base ran the Ollama library default, an inference of effectively Q4 rather than a recorded value. The R24 and clean figures are computed over the whole reply; scoring only the fenced code lifts this model's base clean rate from 6.7% to 20.0% and the pooled base rate from 6.7% to 10.0%, without closing the gap. And tool calling was dropped as a product decision, so this is a chat-only model. | |
| That said, this is the honest limit of what the model buys you. It has the lowest clean rate and the lowest composite (0.830) of the four builds in the head-to-head, and it is the only one where a stock base given the whole rule book is roughly level with the fine-tune (73.3% clean against 80.0%) and actually scores higher on mean R24 in that condition (99.80 pooled, 99.75 over delivered, against 99.41). Pick it if you want a Gemma lineage or a second opinion that is not correlated with the Qwen models. If you have not decided, the line's default is stcoder-qwen25-7b at Q6_K (5.82 GiB); take stcoder-qwen25-14b at Q4_K_M (8.37 GiB) instead if rule-cleanliness matters more than speed. Either way the code is a draft: compile it in LogicStudio and read the sequence logic before it goes near a machine.</p> | |
| </section> | |
| <!-- APPENDIX --> | |
| <section> | |
| <div class="sh"><span class="sn">A</span><h2>Appendix — the test prompts</h2></div> | |
| <div class="tw"><table> | |
| <thead><tr><th>ID</th><th style="text-align:left">Prompt as sent to the model</th></tr></thead> | |
| <tbody><tr><td><code>analog_scale_raw_to_bar</code></td><td style="text-align:left">We have a 4-20 mA pressure transmitter landing on an analog input card. The raw word comes in as an integer where 5530 counts = 4 mA and 27648 counts = 20 mA, and the transmitter is ranged 0-16 bar. Can you write the Structured Text that turns the raw count into bar, keeps the result inside 0 to 16, and sets a wire-break flag when the loop current drops under about 3.6 mA (roughly 4977 counts)? There is also an enable bit coming from the scan logic - when that is off the scaled output should read zero.</td></tr><tr><td><code>setpoint_clamp_and_ramp</code></td><td style="text-align:left">Operators type a hot water setpoint on the HMI. Anything below 40.0 degC or above 85.0 degC has to be pulled back to the nearest limit and an out-of-range warning bit raised, which the operator can acknowledge once the entry is sensible again. On top of that, the setpoint that actually goes to the controller must not jump more than 2.0 degC per PLC cycle, so ramp it toward the clamped target. Structured Text please.</td></tr><tr><td><code>heater_power_avg_squared</code></td><td style="text-align:left">Heater panel job. I sample the load current every scan and it is noisy, so I need the average of the last 8 samples first. From that averaged current I want the dissipated power using P = I squared times R, with R = 4.7 ohm. If the power stays above 1500 W for 3 seconds, latch an overload bit that only clears when the operator hits the reset pushbutton. Please write it in Structured Text.</td></tr><tr><td><code>timer_start_delay</code></td><td style="text-align:left">We've got a conveyor with a pre-start warning horn. When the operator hits the start button I need the horn to sound for 5 seconds first, then the motor kicks in and the horn goes quiet. Pressing stop, or any fault coming in, has to drop the motor and the horn straight away. Can you write the Structured Text for this?</td></tr><tr><td><code>timer_blink_oscillator</code></td><td style="text-align:left">Need a flashing warning beacon done in ST. While the machine warning flag is active the lamp should blink continuously - lit for 500 ms, then dark for 1.5 seconds, over and over. When the warning clears the lamp stays off. There's no hardware flasher relay on this panel so it has to be done in software.</td></tr><tr><td><code>timer_offdelay_coastdown</code></td><td style="text-align:left">Our extraction fan needs a run-on timer. The fan should run whenever the main motor is running, and then keep running for 30 seconds after the motor is switched off so the duct clears. Emergency stop has to cut the motor and the fan immediately, with no run-on. Also give me a flag I can put on the HMI to show that the purge is in progress. Structured Text please.</td></tr><tr><td><code>io_terminal_seal_in</code></td><td style="text-align:left">On a small booster pump skid the start pushbutton is wired to the first digital input on the CPU and the pump contactor sits on the first relay output. There is an NC stop button on the second input and the motor thermal overload aux contact on the third. Write me the ST for a normal start/stop seal-in: the start button is bouncy so it has to be held for half a second before the pump picks up, an overload trip must latch the pump out, and the lockout should only clear when the operator presses stop with the overload already reset.</td></tr><tr><td><code>status_word_bit_mask</code></td><td style="text-align:left">We poll a 16-bit status word from a VFD over Modbus TCP. The drive manual gives the bit masks in hex: 16#0001 = ready, 16#0002 = running, 16#0008 = fault, 16#0080 = at speed, and 16#0F00 holds a 4-bit warning code. Break the word out into individual booleans, pull the warning code out as a plain integer 0-15, and give me a single 'drive healthy' flag that is only true when it is ready and running with no fault and no warning. If the Modbus poll is not valid, do not trust any of it.</td></tr><tr><td><code>first_out_fault_annunciator</code></td><td style="text-align:left">I need a first-out fault annunciator for a compressor package. The trip inputs are low oil pressure, high discharge temperature, motor overload and high vibration. Whichever one trips first has to be captured and reported to the HMI so the operator can tell at a glance which fault actually caused the trip - later trips must not overwrite it. I also want a common alarm lamp, a horn that goes quiet when acknowledge is pressed while the lamp stays on, and a reset that is only accepted when all four inputs are clear again. The compressor run permissive drops out on any fault.</td></tr><tr><td><code>machine_cycle_full_program</code></td><td style="text-align:left">Can you write the complete program for our drilling station cycle? One part per cycle: when the guard is closed, a part is present and there are no alarms, pressing Start closes the clamp, waits 1.5 s for clamp pressure to build, then the drill motor starts and the head feeds down until the down limit switch, dwells 3 s at depth, retracts until the up limit, opens the clamp after 0.8 s, and the station goes back to idle ready for the next part while incrementing the part counter. E-stop or drill overload must drop every output and put the station into a fault state that only the reset button clears. I want the step sequencing and the timers in there.</td></tr><tr><td><code>reusable_pump_block</code></td><td style="text-align:left">We have three identical booster pumps on a skid and I am tired of copy-pasting the same logic three times. Give me one reusable pump control block that I can instantiate once per pump. Per instance: run request, motor thermal overload contact, dry-run (low suction) switch, fault reset. Outputs: motor run command, fault flag, and a fault code for the HMI so the operator can see what tripped. Behaviour: 2 s start delay after the run request, the dry-run switch is only evaluated 5 s after the motor has actually started, overload trips immediately, any trip latches until it is reset, and once the pump stops it has to stay off for at least 10 s before it may restart. Also show me how it gets called for the three pumps.</td></tr><tr><td><code>analog_scale_helper</code></td><td style="text-align:left">I need a small reusable routine that turns the raw analog word from our pressure transmitters into engineering units, because I have to do the same conversion for three transmitters on the line. They are 4-20 mA into a 0-32767 count input card, so 6553 counts is 4 mA and 32767 counts is 20 mA, and the measuring range is 0-250 bar. It should clamp the result to the range and set an out-of-range flag, plus a broken-wire flag when the raw count drops below 3200 (under 2 mA). A rounded integer in tenths of a bar for the HMI would be handy too.</td></tr><tr><td><code>retain_counter_power_cycle</code></td><td style="text-align:left">Line 3 bottler: a photo eye (bPartDetect) pulses once for every bottle that goes past. I need two totals out of it - a lifetime total that goes into the maintenance records, and a shift total that the supervisor clears with bShiftReset at the start of each shift. The problem is we power the cabinet down every night and in the morning both totals come back as zero, so quality has no numbers to report. Write the ST for the counting logic so the lifetime total is still correct after the panel comes back up.</td></tr><tr><td><code>shared_project_value</code></td><td style="text-align:left">Tank farm project. The level control block works out the tank level in percent and a plant-running flag. The alarm block, the Modbus reporting block and the HMI block all need to see exactly the same two values on every scan - right now each of them recalculates them on its own and they end up disagreeing with each other. Write the level control block so those two values are produced in one place and the rest of the project can use them. Inputs are rLevelRaw from the transmitter (already scaled 0-100), bPumpRunning, bValveOpen and bEStop; the published percent should stay inside 0-100 and a transmitter reading outside range has to be flagged.</td></tr><tr><td><code>machine_data_grouping</code></td><td style="text-align:left">I have three identical packaging machines in one project. For each machine I track a temperature setpoint, the actual temperature, the belt speed, a cycle counter and a state code. I would like each machine's values kept together so I can run the same monitoring logic over all three in a loop instead of copy-pasting the same block three times. Logic per machine: if it is in the running state and the actual temperature is more than 5 degrees off setpoint, or the belt speed drops below 10, fault it; count a cycle on each rising edge of that machine's cycle-done input; EStop faults all of them. Also give me how many machines are currently faulted. Write it in ST.</td></tr></tbody> | |
| </table></div> | |
| </section> | |
| <footer> | |
| <b>Licence.</b> apache-2.0. Fine-tuned from <code>google/gemma-4-12b-it</code>; the base | |
| model's attribution and NOTICE are preserved. You may use, modify and redistribute under the | |
| same terms.<br><br> | |
| <b>Training data.</b> A curated in-house multi-task PLC corpus (approximately 2,930 training and | |
| 550 validation examples) focused on Structured Text generation. The dataset is not publicly | |
| released.<br><br> | |
| <b>A note on tags.</b> The reproduction tags below use an underscore | |
| (<code>stcoder_gemma4-12b:q6</code>) because that is how the study's local Ollama models were named. | |
| The tag you create by following section 3 uses hyphens | |
| (<code>stcoder-gemma4-12b</code>) — the name is arbitrary and does not affect the weights.<br><br> | |
| <b>Exact builds compared.</b> Fine-tune <code>stcoder_gemma4-12b:q6</code> versus stock base | |
| <code>gemma4:12b</code>, both served by local Ollama. The base arm's quantisation was | |
| <b>not recorded</b> in the run data; where this page says the base ran "effectively Q4" that is an | |
| inference from the Ollama library default for that tag, not a measured value.<br><br> | |
| <b>Reproducibility.</b> Harness <code>scripts/compare/paper_eval.py</code>; aggregation | |
| <code>paper_analyze.py</code>; violation localisation <code>paper_contrasts.py</code>; prompt set | |
| <code>dataset/paper_eval_suite_v1.json</code>; raw rows <code>results/paper/results_v3.jsonl</code>. | |
| Every figure on this page is generated from those rows by | |
| <code>scripts/compare/build_model_articles.py</code>.<br><br> | |
| <b>Where the tool-calling numbers come from.</b> The tool-call composites quoted on this page | |
| were measured in a separate internal evaluation that is not part of this release, not in the | |
| 240-generation code study, so they cannot be traced to the rows above. The same is true of the | |
| per-quantisation figures in section 9, whose code holdout overlapped the training | |
| distribution by roughly 84%. | |
| </footer> | |
| </div> | |