NLA Activation Verbalizer β€” Qwen3 4B, universal multi-layer (SFT)

LoRA adapter that turns a residual-stream activation vector from Qwen3-4B into a short natural-language rendering of what the model was processing at that layer. A single adapter handles all 36 layers: the prompt carries the extraction depth (from depth {N}%), so one set of weights spans early-syntax bands through near-output bands.

Part of the nla-at-home project β€” a DIY replication of Anthropic's Natural Language Autoencoders using open-weight models. This is the smallest universal NLA in the series (4B; see the Qwen 2.5 7B trio for the larger sibling).

This is the SFT stage. For the sharper, GRPO-refined variant see nla-qwen3-4b-universal-av-grpo; the companion reconstructor is nla-qwen3-4b-universal-ar.

Injection protocol (the part everyone gets wrong)

parameter value
injection token ㈎ (U+320E), id 149705
injection method normalize the vector's L2 norm TO 150.0 (never multiply BY 150)
prompt chat template around the AV prompt, add_generation_prompt=True, encoded with add_special_tokens=False
depth from depth {N}% where N = nearest of 4,10,17,25,32,40,47,55,63,71,80,90,96 to layer*100/36
chat template Qwen3: pass enable_thinking=False
# activation: torch tensor [2560] read at block L's output, last prompt token
from nla_lib import make_av_prompt, normalize_activation, nearest_depth_pct
content = make_av_prompt(nearest_depth_pct(L, 36), "㈎")
chat = tok.apply_chat_template([{"role": "user", "content": content}],
                               tokenize=False, add_generation_prompt=True,
                               enable_thinking=False)
ids = tok.encode(chat, add_special_tokens=False)
emb = model.get_input_embeddings()(torch.tensor([ids]).to(dev)).clone()
emb[0, ids.index(149705), :] = normalize_activation(activation).to(emb.dtype)
out = model.generate(inputs_embeds=emb, attention_mask=torch.ones(1, len(ids)).to(dev),
                     max_new_tokens=120, do_sample=False)

Full working scripts: scripts/eval_roundtrip_universal.py, scripts/describe_live.py in the repo.

Training

LoRA SFT over ~68k terse, content-anchored descriptions (_twin_clean set; lr 8e-6, 5-epoch schedule β€” best checkpoint is epoch 1, later epochs did not improve validation) of activations from the project's 5.2k-text public corpus (corpus v2), extracted at the last prompt token. 528 texts held out (val_text_ids.json ships with the adapter).

Evaluation β€” round-trip through an independent reconstructor

Descriptions are scored by whether the companion AR can rebuild the original activation from the text alone (centered cosine, per-layer mean removed; 284-text clean holdout never seen by AV, AR, or the GRPO curriculum):

layer (depth) centered cosine
L0 (4%) 0.014
L4 (10%) 0.150
L9 (25%) 0.373
L13 (40%) 0.419
L18 (47%) 0.575
L22 (63%) 0.546
L26 (71%) 0.581
L31 (90%) 0.545
L35 (96%) 0.271
mean 0.386

The extreme layers (L0, L35) are weak for both this adapter and the GRPO variant β€” a property of the substrate at this scale, not of the training stage. The GRPO variant improves every layer β€” see its card for the side-by-side.

Limitations

Trained on the public, safety-filtered corpus split: descriptions of harmful / NSFW / manipulative activations are out of distribution and will be weaker (see the repo's "Safety & scope" note). Entity fidelity degrades for rare proper nouns; gist and structure are the reliable layer. The SFT stage is prone to repetition loops on some inputs β€” fixed by the GRPO stage.

Downloads last month
49
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for anicka/nla-qwen3-4b-universal-av

Finetuned
Qwen/Qwen3-4B
Adapter
(1074)
this model

Collection including anicka/nla-qwen3-4b-universal-av