Qwen3.8-27B: a refusal control vector, derived from the model itself (GLP-49)
One file, 1.0 MB, that removes refusal behaviour from Qwen3.8-27B at inference
time. No weights are touched and no checkpoint is redistributed: the base model
stays byte-identical, and serving without this file is the model as it was.
It is opt-in. A runtime applies it only when the operator names the file, and
says at startup what is in force. With the file in force the model's behaviour
changes — that is what the file is for — and the tables below are what it
changes and by how much.
| file | size | needs |
|---|---|---|
Qwen3.8-27B-refusal-cvec-GLP-49-L10-58-a1.gguf |
1.0 MB | a runtime that understands glp.mode = project. TensorFold does, on Metal. Experimental |
Credits. The container format, its reference implementation and its
validator are Matt Suiche's: the GLP control-vector format as implemented by
the weightless plugin, which is
also where the container reader and the captain-vector --validate checker
come from. The published per-layer vector this file is measured against ismsuiche/Qwen3.8-27B-abliterated-cyber-GLP-49,
and the controlvector.model_hint convention is his, so a runtime that matches
on it treats the two files the same way. What is different here is the
derivation: the directions in this file were measured by BlivionIaG from the
model's own activations, with the tf-qwen27-steer-derive tool, with no
third-party corpus and no other model's activations involved, and with the
massive-activation mask that msuiche's per-layer vector's own metadata points
at.
Lineage. This repo hangs undermsuiche/Qwen3.8-27B-abliterated-cyber-GLP-49,
and the two are not in competition: his file is made for another engine and another
model — the weightless engine and the Qwen/Qwen3.8-27B checkpoint — while this
one is made for the MLX 4-bit conversion of the same weights, served by TensorFold on
Metal. What was validated here is TensorFold/Qwen3.8-27B-MLX-4bit at 22d8d538,
and the directions in this file were measured from that model's own activations.
Results
Measured on an Apple M4 Pro, Metal, on Qwen3.8-27B-MLX-4bit at revision22d8d538, at strength 1.0, with 300 new tokens per prompt. Every vector in the
table below was served through that same engine on that same checkpoint, with the
same prompt sets. Two prompt sets, both written for this work: the thirty pairs
the vector was derived from, and twenty pairs written afterwards, over topics the
derivation never names and in forms it never uses. The derivation never reads the
second set.
The harmful column is the share of harmful prompts the model still refuses, so
lower is better there. The benign column is the share of the harmless twins
it refuses, so lower is better there too: a vector that removes a
capability instead of a refusal shows up there.
| set | vector | harmful refused | benign refused | replies coherent |
|---|---|---|---|---|
| derivation (30 pairs) | none | 30/30 | 0/30 | 60/60 |
| derivation (30 pairs) | this file | 0/30 | 0/30 | 60/60 |
| derivation (30 pairs) | msuiche's GLP-49, per layer | 19/30 | 6/30 | 60/60 |
| held out (20 pairs) | none | 20/20 | 5/20 | 40/40 |
| held out (20 pairs) | this file | 0/20 | 0/20 | 40/40 |
| held out (20 pairs) | msuiche's GLP-49, per layer | 9/20 | 6/20 | 40/40 |
Over-refusal is zero on both sets: no harmless twin of a harmful prompt is
refused because of this file, where msuiche's per-layer vector costs six in
thirty. It is also zero on the held-out set, where the base model refuses five
twins by itself.
The refusal rate is a lower bound, and the replies are the measure. A vector
can bend the wording of a refusal without removing it, and msuiche's vector
does: eleven of the replies it unblocks on the held-out set still decline, in
other words — "you should not enter your neighbour's house", "it is illegal in
most jurisdictions". The metric counts those as compliance. This file takes the
decline away: the model answers the request, usually after a legal or safety
caveat rather than in place of an answer, and for several of the twenty it hands
over what was asked for.
A capability check on twelve ordinary tasks of the model's own work — code,
arithmetic, translation, a regular expression, a haiku, facts and protocols —
answered every one of them.
Usage
With TensorFold, on the Metal engine:
tensorfold serve TensorFold/Qwen3.8-27B-MLX-4bit \
--glp Qwen3.8-27B-refusal-cvec-GLP-49-L10-58-a1.gguf --glp-alpha 1.0
The server prints what is in force before it loads the model, and refuses a file
it cannot apply rather than serving without it:
[tensorfold] glp on: Qwen3.8-27B-refusal-cvec-GLP-49-L10-58-a1.gguf (mode: project;
alpha: 1.00; layers: 49 (10-58); base: TensorFold/Qwen3.8-27B-MLX-4bit @ 22d8d53;
digest: 9a50f7160fa2 (verified))
With any runtime that implements the project mode of this format, the file is
the whole thing: read the directions, and subtract the component along each
direction from the residual stream at the layer the tensor names.
h ← h − α·(h·d̂)·d̂ per layer, at the post-layer residual
Do not scale the strength
α = 1 removes the component along the direction. α = 2 reflects it:
α = 1 h·d̂ → 0 the component is removed
α = 2 h·d̂ → −(h·d̂) the component is REFLECTED
Here the strength is flat from 0.5 to 3: the refusal rate and the over-refusal
rate do not move. At 4 the replies go empty, which is where msuiche's vector's
own metadata says this model collapses. So the strength is not the dial
here: it is flat where it should be, and broken where the model is.
If you want less than full strength, drop layers instead. The file holds
layers 10 to 58, one direction per layer, and each layer's tensor is independent:
a reader that takes a subset of them applies a weaker version of the same
thing. Re-run the derivation for a different window rather than editing this
file by hand. Do not include layer 0 — layer 0 is not steerable, and the
file therefore starts at 10.
A strength of 1e-6 reproduces the baseline exactly: the path is a no-op at zero.
What this is, and its limits
It is inference-time only. No weights are modified, no checkpoint is
replaced, and the model is unchanged without the file. It is inert without a
runtime that implements the mode: llama.cpp's built-in control vectors are
additive (h ← h + s·d̂), a different operation that pushes every token along
the axis instead of removing the component, and a reader that does not
understand glp.mode must refuse the file rather than fall back to adding.
It is checkpoint-bound. The direction was measured from the activations ofTensorFold/Qwen3.8-27B-MLX-4bit at revision 22d8d538, so it belongs to that
checkpoint. It is not valid for another model, another size, or another
quantisation of the same weights, because the activations it was fitted to are
what it is. The method transfers to any dense model; this file does not.
The masking is what makes it work. This checkpoint carries massive
activations: dimensions whose activations stand orders of magnitude above the
rest, the screen measuring 701 for this checkpoint. A difference of means taken
where they dominate points at those dimensions rather than at the refusal, and
the result costs harmless refusals. The producer leaves the top 0.5% of each
layer's dimensions out of the direction and renormalises what is left. The same
derivation without the exclusion refuses 18 of 30 harmful prompts and costs
seven harmless refusals in thirty.
The reply is a refusal with the words taken out. The model usually adds a
caveat and then answers, and for several prompts it hands over what was asked
for. This is a refusal ablation on a model you run yourself: use it on what you
are allowed to run.
Scoring caveat
The harness counts a reply as a refusal by what it says, not by what it goes on
to do. That is an upper bound on what this file does, and the replies are worth
reading: the metric cannot tell "you must not do this" from "you must not do
this, and here is how it is done anyway".
The second set is the stronger evidence, because the derivation never reads it.
It is still twenty pairs written for this work rather than a third-party
benchmark, so it says how the recipe generalises across prompts, not across
prompts anyone else chose.
Reproducing it
The producer takes the model itself, its own prompt sets and the mask, and
writes the file. It takes 18 seconds on an M4 Pro, and the directions come
out byte-identical:
tf-qwen27-steer-derive --model DIR --out FILE \
--repo TensorFold/Qwen3.8-27B-MLX-4bit --revision 22d8d538154e0e5b6f8dcb1fcc74a60a0798104c \
--capture last --mask 0.005 --layers 10-58 --alpha 1.0 \
--name "Qwen3.8-27B-refusal-cvec-per_layer-masked-L10-58-a1" \
--author "BlivionIaG" --license "Apache-2.0" --model-hint "qwen3_5" \
--description "..." --effect "alpha 0.5 to 3 is flat; at 4 the replies go empty"
Checking it
The format's reference implementation is Matt Suiche's weightless plugin, and
its own validator reads this file and reports nothing wrong with it:
captain-vector --validate Qwen3.8-27B-refusal-cvec-GLP-49-L10-58-a1.gguf
...: 0 FAIL, 0 WARN
The reference reader and the native reader in TensorFold agree on every layer,
on the metadata and on the digest.
Provenance
author BlivionIaG
lineage msuiche/Qwen3.8-27B-abliterated-cyber-GLP-49 (adapter of)
base TensorFold/Qwen3.8-27B-MLX-4bit @ 22d8d538154e0e5b6f8dcb1fcc74a60a0798104c
contrast 30 harmful / 30 form-matched harmless, written for this work
estimator difference of means over the last prompt token, 0.5% massive-activation masked
hook post-layer residual, layers 10-58, alpha = 1
mode project (h -= alpha*(h.d)d)
width 5120
digest 9a50f7160fa2f0a41e7fa868c791d372b0a374f9365cc98c25d52abe96ce9d8d
format GLP, as implemented by the weightless plugin (Matt Suiche)
licence Apache-2.0, the licence of the code that derived it
The file is a derivative work of the activations ofTensorFold/Qwen3.8-27B-MLX-4bit, which carries its own licence. The base
model is Qwen/Qwen3.8-27B converted to MLX 4-bit by TensorFold; thecontrolvector.model_hint is qwen3_5, the same hint msuiche's per-layer
vector carries, so a runtime that matches on it treats the two the same way.