File size: 11,591 Bytes
7c6176b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | # SecureCoder β training runbook
A QLoRA fine-tune that targets three skills at once: **coding**, **tool calling**
and **cybersecurity** (offence *and* defence), on a base chosen to train cheaply
and run fast. Everything here is measured or verified on real jobs, not assumed β
each claim carries the job id you can inspect.
## 1. Base model choice
Screened on the Hub on 2026-09-24 (params / licence / tool-calling support):
| Candidate | Params | Licence | Verdict |
| --- | --- | --- | --- |
| **`Qwen/Qwen3-Coder-30B-A3B-Instruct`** | 30.5B MoE, ~3B active | Apache-2.0 | **chosen** β code-specialised, native tool-calling template, MoE means it *trains* like a 3B model and fits 48 GB in 4-bit |
| `Qwen/Qwen3.8-27B` | 27.8B dense | Apache-2.0 | best raw quality, ~4x slower per step; good upgrade path once the pipeline is proven |
| `Qwen/Qwen3-Coder-Next` | 79.7B | Apache-2.0 | needs an 80 GB card (A100 80 GB `$2.50/h`); too big for the first run |
| `Qwen/Qwen3.8-Flash-Next` | 180B | **license: other** | rejected β non-commercial-style licence on an 180B model |
| `openbmb/MiniCPM5-2B` | 2.5B | Apache-2.0 | `tool-calling` tag, tiny; good for a T4 smoke test, weak ceiling |
| `TokenRhythm/NeoHorse-1-9B` | 9.0B | Apache-2.0 | `tool-use`, `reasoning`; the fallback if T4-only |
Why not start from an existing *abliterated* checkpoint? Because refusal removal is
a separate weight-edit (section 6) that can be applied to **any** checkpoint after
fine-tuning, and pre-abliterated bases are community re-uploads with unclear
provenance. Fine-tuning first keeps the lineage clean and the base licensed.
## 2. Data mix
The cap per source *is* the recipe β sources differ hugely in size, so the mix is
balanced by taking a fixed slice of each. Verified on a real CPU job
(`Taimwe/6ab5ae256b030d633f68faef`) at 25 rows/source:
| Source | Rows taken | Kind | What it teaches |
| --- | --- | --- | --- |
| `NousResearch/hermes-function-calling-v1` `[func_calling]` | 9,000 | tool calling | full tool-call conversations + JSON schemas |
| `NousResearch/hermes-function-calling-v1` `[func_calling_singleturn]` | 3,000 | tool calling | picking the right function, no chatter |
| `lockon/xlam-function-calling-60k` | 10,000 | tool calling | 60k API-call pairs (query β call) |
| `ise-uiuc/Magicoder-OSS-Instruct-75K` | 10,000 | coding | self-instruct problems + solutions |
| `Trendyol/Trendyol-Cybersecurity-Instruction-Tuning-Dataset` | 8,000 | security | security instruction tuning |
| `AlicanKiraz0/Cybersecurity-Dataset-Fenrir-v2.1` | 5,000 | security | broad security Q&A |
| `Humanlearning/CyberSecurity_OWASP-sft-dataset` | 3,000 | secure coding | OWASP / defensive coding |
| `MrClipperz134/CTF-Instruct` | 3,000 | CTF | challenge instruction β solve |
| `TrueNix/ctf-solver-dataset` | 3,000 | CTF | solving trajectories |
| `mlabonne/FineTome-100k` | 3,000 | replay | general chat, so instruction-following does not collapse |
**Total β 57,000 rows, ~30% of them tool-calling.** Every source validated at
25/25 kept with zero render failures.
Excluded deliberately:
* `dpevzner/Cybersecurity_Reasoning_Dataset` β its builder configs were renamed
(`default` β `mistral|deepseek|chatml|gemma`) and the fields the card documents
(`unified_interpretation`) are empty in the published revision, so every row was
dropped. Re-addable once its schema settles.
* Anything whose purpose is building malware or weaponised exploits. Recon and
enumeration knowledge, exploit *concepts*, CTF solving and defensive
engineering are in; end-to-end attack tooling is not. This is a deliberate
scope line, not an oversight.
## 3. What training does with the data
Each row is converted to chat messages and rendered through the model's own chat
template, so the model learns the exact format it will be asked for at inference
time (`<|im_start|>β¦<tool_call><function=NAME><parameter=β¦>` for Qwen3-Coder).
Two format traps found the hard way and handled in code:
* Qwen3-Coder iterates `tool.parameters.properties` β tool schemas must be **flat**
(`{"name", "description", "parameters"}`), not wrapped in `"function"`.
* It also iterates `tool_call.arguments | items`, so `arguments` must be a
**mapping**. A JSON string there fails with *"Can only get item pairs from a
mapping"* β which is exactly how xLAM was silently contributing 0 rows until fixed.
`render_record()` therefore detects the template's convention once (flat/nested Γ
dict/string) and reuses it, instead of hard-coding one model's dialect.
## 4. Compute and cost
Verified `hf jobs hardware` prices (per hour), 2026-09-24:
| Flavor | VRAM | $/hour | Use |
| --- | --- | --- | --- |
| `cpu-basic` | β | $0.01 | data-mix validation, class checks |
| `t4-small` | 16 GB (T4) | $0.40 | plumbing checks; too small for a 30B in 4-bit |
| `l4x1` | 24 GB | $0.80 | 9B runs on a budget |
| `a10g-large` | 24 GB | $1.50 | 9Bβ14B comfortably |
| **`l40sx1`** | **48 GB** | **$1.80** | **the 30B-A3B QLoRA** |
| `a100-large` | 80 GB | $2.50 | faster per step, larger batch |
| `rtx-pro-6000` | 96 GB | $2.75 | headroom for longer context |
| `h200` | 141 GB | $5.00 | full fine-tune territory, overkill here |
Rough budget for the full 1-epoch run. **These are now measured, not guessed** β the
smoke test (`Taimwe/6ab5b3ed52d0dbd7f1d8d454`, a100-large) trained at
**0.8 rows/s** (20 s per step at batch 2 Γ grad-accum 8, max seq 2048, packing on),
which includes a few minutes of `torch.compile` warm-up that does not recur per step:
| Mix | Rows | Time at 0.8 rows/s | Cost on `l40sx1` ($1.80/h) |
| --- | --- | --- | --- |
| full mix as specified | ~57,000 | ~20 h | **~$36** |
| half caps (`--max-steps` or edit the table) | ~28,000 | ~10 h | ~$18 |
| lean mix (cut caps to ~1/3) | ~19,000 | ~7 h | ~$12 |
Because warm-up inflates that rate, a **300-step measured run** (~$4 on a100-large)
gives a trustworthy steady-state number before committing to a long job. Levers that
move the cost most, in order: rows in the mix, `--max-seq-length` (2048 vs 4096),
`--batch-size` (raise it on 80β96 GB cards), `--lora-r`.
The `--smoke` run itself costs under $1 and proves model load, data rendering,
training, evaluation and the Hub push end to end.
No local GPU needed. Colab works the same way with `uv run train_securecoder.py β¦`
(Colab Free's T4 cannot hold a 30B in 4-bit β use Colab Pro L4/A100, or point
`--base-model` at a 9B).
## 5. Runbook (copy-paste)
```bash
# 0. auth (token needs repo.write, and job.write for Jobs)
hf auth login --token hf_xxx
# 1. validate the mix: 25 rows/source, no GPU, ~50 s, ~$0.0002
hf jobs run -d --flavor cpu-basic --timeout 30m --name validate-mix \
ghcr.io/astral-sh/uv:python3.12-bookworm \
uv run --no-project https://huggingface.co/Taimwe/securecoder-scripts/resolve/main/train_securecoder.py \
--validate-only --validate-per-source 25 --show-samples 3
# 2. smoke test on a real GPU: 200 rows/source, 20 steps, pushes an adapter (~$0.50)
hf jobs run -d --flavor l40sx1 --timeout 40m --secrets HF_TOKEN --name smoke-train \
ghcr.io/astral-sh/uv:python3.12-bookworm \
uv run --no-project https://huggingface.co/Taimwe/securecoder-scripts/resolve/main/train_securecoder.py \
--smoke --output-repo Taimwe/securecoder-smoke --private --report-to none
# 3. the real run (~$9β14)
hf jobs run -d --flavor l40sx1 --timeout 12h --secrets HF_TOKEN --name securecoder-run1 \
ghcr.io/astral-sh/uv:python3.12-bookworm \
uv run --no-project https://huggingface.co/Taimwe/securecoder-scripts/resolve/main/train_securecoder.py \
--num-epochs 1 --output-repo Taimwe/securecoder-30b-pro \
--trackio-space Taimwe/securecoder-trackio
# monitor
hf jobs inspect Taimwe/<job_id> --json
hf jobs logs Taimwe/<job_id>
hf jobs stats Taimwe/<job_id>
hf jobs cancel Taimwe/<job_id>
```
### Platform traps already worked around (verified 2026-09-24)
| Symptom | Cause | Workaround |
| --- | --- | --- |
| `failed to set MOUNT_ATTR_IDMAP on /usr/bin/nvidia-cuda-mps-control` on a GPU flavor | `hf jobs uv run` mounts an artifacts bucket; the idmap mount fails on some GPU nodes (reproduced twice, 4 s after start) | use `hf jobs run` + the script's **public Hub URL** (works: `Taimwe/6ab5ad3c6b030d633f68face`) or a read-only repo mount `-v hf://models/Taimwe/securecoder-scripts:/scripts:ro` (works: `Taimwe/6ab5ad3d52d0dbd7f1d8d286`) |
| inner quotes stripped from `python -c "β¦"` | PowerShell native-argument quoting | use a UV script file, never `-c` one-liners |
| `charmap codec can't encode` while reading logs | Windows console encoding | set `$env:PYTHONIOENCODING='utf-8'` before `hf jobs logs` |
| HF's trainer skill says Jobs need a paid plan | docs caveat | not enforced on this account β CPU and GPU jobs both ran |
## 6. Turning down refusals (optional, after SFT)
Abliteration is a weight edit, not a training run: find the "refusal direction" in
the residual stream and project it out of the writing matrices.
1. **`Heretic`** (p-e-w/heretic) β the tool behind the recent wave of
`*-heretic-abliterated-*` checkpoints (e.g.
`culturerevolt/gemma-4-12b-heretic-abliterated-GGUF`, 174k downloads). Runs on
the merged 16-bit model and optimises the ablation to trade refusal rate against
KL divergence, so it preserves capability better than a hand-rolled ablation.
2. **Manual directional ablation** (Arditi et al., *Refusal in LLMs is mediated by a
single direction*): collect residual activations for harmful vs. harmless
prompts, take the mean difference per layer, and zero that component in
`o_proj` / `down_proj`.
Honest expectations: **it is not free.** Refusals drop, and some performance on the
same layers' other duties drops with them; scope-setting behaviour ("this needs
authorisation") can weaken too. Measure both sides (section 7) and document the
result on the model card, or you are shipping an unmeasured change.
## 7. Evaluation before publishing
1. **Tool-call validity** β 200 prompts with real tool schemas; parse the emitted
`<function=NAME><parameter=β¦>` block back to JSON and score syntax validity,
correct function choice, and schema-conformant arguments. This is the number
that matters most for "top-notch tool calling".
2. **Code sanity** β generate 100 solutions, check compilation, run the bundled
tests on a small HumanEval-style subset.
3. **Security knowledge** β fixed MCQ/triage set (e.g. `CyberNative/CyberSecurityEval`),
scored before *and* after fine-tuning so the mix's effect is visible.
4. **Regression** β the same three on the untouched base, same prompts. A fine-tune
that feels better but scores worse is a regression with extra steps.
5. **Refusal rate** β if abliterated, publish before/after refusal rate next to the
eval deltas.
## 8. Known risks
* MoE LoRA defaults to attention + router (`q/k/v/o/gate`). `--target-modules
all-linear` on a 128-expert model means ~800M trainable parameters β much slower
and hungrier. Start conservative; revisit if security knowledge is the weak score.
* 4-bit QLoRA of a 30B (~17 GB) is tight on a 24 GB card; 48 GB allows batch 2 with
4096-token samples.
* One 1-epoch pass over three domains is a *start*, not a finished model. Iterate on
the mix ratios β they live in one table at the top of the script.
* Nothing here adds safety training, and the security slice includes
recon/enumeration knowledge framed for authorised use. Publish explicit
intended-use and limitations sections; do not present it as a hardened model.
|