demo: apply_mode kwarg for delivery-mode demos + cthulhu-mini cowsay readout

- show_steer(apply_mode=, apply_span=) swaps delivery (add|clamp|add_last|
  replace_last) by rebuilding the cfg, no re-extraction -- delivery is decoupled
  from extraction (applies.py), so the demo layer is where you pick the mode
- j-space readout now speaks from a mini cowsay bubble (^(;,;)^)
- word_steering.ipynb: new 'Delivery modes' section, one demo per mode, each
  with its C=0 semantics called out (clamp C=0=ablation, replace_last C=0=zero)

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-07-10 20:34:01 +08:00
co-authored by Claudypoo
parent 00395ce398
commit 07759f73ce
2 changed files with 695 additions and 37 deletions
+28 -6
View File
@@ -10,8 +10,11 @@ output is debuggable and nothing is parsed or reconstructed.
"""
from __future__ import annotations
import dataclasses
import torch
from loguru import logger
from steering_lite import Vector
from .jacobian import Jacobian
@@ -22,22 +25,41 @@ def chat_input(tok, user_msg: str, *, enable_thinking: bool = True) -> str:
add_generation_prompt=True, tokenize=False, enable_thinking=enable_thinking)
def _cthulhu_say(text: str) -> str:
"""The j-space readout in a mini cowsay bubble -- Cthulhu speaks the tokens
the steered residual points to. Cosmetic; the tokens are the payload."""
n = len(text) + 2
return ("\n".join([" " + "_" * n, f"< {text} >", " " + "-" * n,
" \\", " ^(;,;)^"]))
@torch.no_grad()
def show_steer(jac: Jacobian, model, tok, vec, user_msg: str, *,
Cs=(-6, 0, 6), layer: int | None = None, k: int = 6,
max_new_tokens: int = 512, seed: int = 0) -> None:
max_new_tokens: int = 512, seed: int = 0,
apply_mode: str | None = None, apply_span: int = 1) -> None:
"""One block per C: lens readout at `layer`, then the raw generation, all
under steering. Uses the model's own generation_config sampling; `seed`
fixes it so the C blocks are comparable. `layer` defaults to the top fitted
layer. max_new_tokens defaults to 512 so Qwen3's <think> block can close;
256 truncates mid-reasoning."""
256 truncates mid-reasoning.
Extraction is decoupled from DELIVERY (see applies.py): pass `apply_mode`
(add | clamp | add_last | replace_last) to swap how v hits the residual
without re-extracting; `apply_span` is the trailing-position width for the
last/replace modes. Coefficient units differ by mode (clamp sets a component
VALUE, add scales a direction), so each mode wants its own Cs."""
if apply_mode is not None:
vec = Vector(dataclasses.replace(vec.cfg, apply_mode=apply_mode,
apply_span=apply_span), vec.shared, vec.stacked)
layer = jac.layers[-1] if layer is None else layer
prompt = chat_input(tok, user_msg)
enc = tok(prompt, return_tensors="pt").to(model.device)
name = getattr(model.config, "name_or_path", "model").split("/")[-1]
# header carries name/method/prompt once; per-C blocks below only vary in C
# header carries name/method/delivery/prompt once; per-C blocks only vary in C
rule = "=" * 72
logger.info(f"\n\n{rule}\n{name} · method={vec.cfg.method}\nprompt: {user_msg!r}\n{rule}")
logger.info(f"\n\n{rule}\n{name} · method={vec.cfg.method} · delivery={vec.cfg.apply_mode}"
f"\nprompt: {user_msg!r}\n{rule}")
# SHOULD: C=0 is the baseline; +C tilts the lens tokens and tone toward the
# concept, -C away; all stay coherent (gibberish = coeff too large).
for C in Cs:
@@ -49,7 +71,7 @@ def show_steer(jac: Jacobian, model, tok, vec, user_msg: str, *,
# raw decode WITH special tokens: real <think>/</think>, <|im_end|> visible,
# nothing parsed or re-wrapped -- debuggable exactly as the model emitted it
gen = tok.decode(out[0][enc.input_ids.shape[1]:], skip_special_tokens=False)
readout = " · ".join(t.strip() for t, _ in jtop)
block = [f"\n--- C={C:+g} " + "-" * 60,
f" lens @L{layer}: " + " · ".join(t.strip() for t, _ in jtop),
gen]
f" lens @L{layer}:", _cthulhu_say(readout), gen]
logger.info("\n".join(block) + "\n")
+667 -31
View File
@@ -4,11 +4,11 @@
"cell_type": "markdown",
"id": "5ef6f624",
"metadata": {},
"source": "# jsteer hello-world: word steering\n\nLoad the model's full Jacobian lens once, then any word vector is an instant CPU\nmatvec:\n\n```\nv_l = unit( J_l^T @ w )\n```\n\nWe load the authors' pre-fitted n=1000 lens from the Hub (raw Salesforce-wikitext,\nthe reference corpus, 1000 prompts, zero compute); `scripts/fit.py` is only for models\nthey don't publish. `w` is a cotangent (a direction at the output: here the mean\nunembedding row of the words you want more or less of). `J_l^T @ w` is the pullback of\n`w`, the standard autodiff name for J-transpose applied to a cotangent, landing the\nconcept as a residual-stream direction. This is the verified extraction method (see the\nREADME evidence section).\n\nWe generate through the model's chat template with thinking on, so `show_steer` can\nshow, per strength C, the j-space readout, the `<think>` trace, and the answer. Runtime\nis steering-lite: `with v(model, C=...): generate(...)`."
"source": "# jsteer hello-world: word steering\n\nLoad the model's full Jacobian lens once, then any word vector is an instant CPU\nmatvec:\n\n```\nv_l = unit( J_l^T @ w )\n```\n\nWe load the authors' pre-fitted n=1000 lens from the Hub (raw Salesforce-wikitext,\nthe reference corpus, 1000 prompts, zero compute); `scripts/fit.py` is only for models\nthey don't publish. `w` is a cotangent (a direction at the output: here the mean\nunembedding row of the words you want more or less of). `J_l^T @ w` is the pullback of\n`w`, the standard autodiff name for J-transpose applied to a cotangent, landing the\nconcept as a residual-stream direction. This is the verified extraction method (see the\nREADME evidence section).\n\nWe generate through the model's chat template with thinking on, so `show_steer` shows,\nper strength C, the j-space readout (in a mini cowsay bubble) and the raw generation\ndecoded with special tokens on, so the model's own `<think>`/`</think>` and `<|im_end|>`\nare visible and nothing is parsed or reconstructed. Runtime is steering-lite:\n`with v(model, C=...): generate(...)`."
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "46973b3d",
"metadata": {
"execution": {
@@ -18,18 +18,82 @@
"shell.execute_reply": "2026-07-10T05:08:54.432236Z"
}
},
"outputs": [],
"source": "# demo notebook authored by Claude\nimport sys\nsys.path.insert(0, \"..\") # repo root for config.py\nimport config # configures loguru on import (compact format, tqdm-safe)\n\nimport torch\nfrom transformers import AutoModelForCausalLM, AutoTokenizer\n\nfrom jsteer import Jacobian, show_steer\n\nMODEL = \"Qwen/Qwen3.5-4B\" # 4B-class: demo material. 0.6B degenerates too easily.\ntok = AutoTokenizer.from_pretrained(MODEL)\nmodel = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.bfloat16).to(\"cuda\").eval()"
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b67d65fc717f4c398812f3f241133ad4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Downloading (incomplete total...): 0.00B [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2e5d7c04cd574f27ae141f8a9342aceb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 2 files: 0%| | 0/2 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "07a53999e8ea494cb854af75e3b1bea4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Loading weights: 0%| | 0/426 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# demo notebook authored by Claude\n",
"import sys\n",
"sys.path.insert(0, \"..\") # repo root for config.py\n",
"import config # configures loguru on import (compact format, tqdm-safe)\n",
"\n",
"import torch\n",
"from transformers import AutoModelForCausalLM, AutoTokenizer\n",
"\n",
"from jsteer import Jacobian, show_steer\n",
"\n",
"MODEL = \"Qwen/Qwen3.5-4B\" # 4B-class: demo material. 0.6B degenerates too easily.\n",
"tok = AutoTokenizer.from_pretrained(MODEL)\n",
"model = AutoModelForCausalLM.from_pretrained(MODEL, dtype=torch.bfloat16).to(\"cuda\").eval()"
]
},
{
"cell_type": "markdown",
"id": "83478eed",
"metadata": {},
"source": "## Load the pre-fitted lens\n\nThe Jacobian is expensive to fit (1 forward + ~d_model/dim_batch backwards per prompt),\nso we skip it: the authors publish n=1000 lenses on the Hub. `Jacobian.from_pretrained`\npulls the `.pt` and wraps it. SHOULD: the repr shows `d_model`, `n_prompts=1000`, and\nall layers `[0..n-1]`. `steer_band` then picks the mid-depth 0.3-0.9 band to steer on."
"source": [
"## Load the pre-fitted lens\n",
"\n",
"The Jacobian is expensive to fit (1 forward + ~d_model/dim_batch backwards per prompt),\n",
"so we skip it: the authors publish n=1000 lenses on the Hub. `Jacobian.from_pretrained`\n",
"pulls the `.pt` and wraps it. SHOULD: the repr shows `d_model`, `n_prompts=1000`, and\n",
"all layers `[0..n-1]`. `steer_band` then picks the mid-depth 0.3-0.9 band to steer on."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "19973ad5",
"metadata": {
"execution": {
@@ -39,8 +103,56 @@
"shell.execute_reply": "2026-07-10T05:08:54.466782Z"
}
},
"outputs": [],
"source": "# The authors' pre-fitted n=1000 lens (raw Salesforce-wikitext, the reference corpus,\n# same estimator jlens fits). Zero local compute. For a model they don't publish, fit\n# your own: scripts/fit.py --model .... The lens spans EVERY layer; steer_band picks the\n# mid-depth 0.3-0.9 band, since steering all layers at once over-drives the residual.\njac = Jacobian.from_pretrained(config.LENS_REPO, filename=config.hub_lens_file(MODEL),\n revision=config.LENS_REVISION)\nband = jac.steer_band(model)\njac"
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "267b10aa51004ffcb806ee1ed685ba6a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Downloading (incomplete total...): 0.00B [00:00, ?B/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "f5fcf8369fee42908d56884ee68a026c",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Fetching 1 files: 0%| | 0/1 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"Jacobian(JacobianLens(d_model=2560, n_prompts=1000, source_layers=[0..30] (31 layers)))"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# The authors' pre-fitted n=1000 lens (raw Salesforce-wikitext, the reference corpus,\n",
"# same estimator jlens fits). Zero local compute. For a model they don't publish, fit\n",
"# your own: scripts/fit.py --model .... The lens spans EVERY layer; steer_band picks the\n",
"# mid-depth 0.3-0.9 band, since steering all layers at once over-drives the residual.\n",
"jac = Jacobian.from_pretrained(config.LENS_REPO, filename=config.hub_lens_file(MODEL),\n",
" revision=config.LENS_REVISION)\n",
"band = jac.steer_band(model)\n",
"jac"
]
},
{
"cell_type": "markdown",
@@ -56,7 +168,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"id": "59ba3763",
"metadata": {
"execution": {
@@ -66,18 +178,40 @@
"shell.execute_reply": "2026-07-10T05:08:54.522508Z"
}
},
"outputs": [],
"source": "# Verified method: pull the words' unembedding direction back through J, on the\n# mid-depth band. +C makes the model say/lean-toward these words, -C away. Instant matvec.\nv = jac.word_vector(model, tok, [\"happy\", \"joy\"], layers=band)"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mI\u001b[0m word cotangent: ['happy', 'joy'] -> first-subtoken ids=[54627, 3987] |w|=0.537\n",
"\u001b[1mI\u001b[0m jacobian_word per-layer |J^T w| (pre-norm): 10:0.553 11:0.601 12:0.628 13:0.66 14:0.673 15:0.695 16:0.7 17:0.717 18:0.678 19:0.659 20:0.689 21:0.713 22:0.752 23:0.769 24:0.782 25:0.814 26:0.847 27:0.815 28:0.762\n"
]
}
],
"source": [
"# Verified method: pull the words' unembedding direction back through J, on the\n",
"# mid-depth band. +C makes the model say/lean-toward these words, -C away. Instant matvec.\n",
"v = jac.word_vector(model, tok, [\"happy\", \"joy\"], layers=band)"
]
},
{
"cell_type": "markdown",
"id": "0717a9b9",
"metadata": {},
"source": "## Pick a coefficient: the coherence/strength tradeoff\n\nThe raw coefficient is model- and lens-dependent, so sweep it. The pre-fitted n=1000\nlens gives a clean, concentrated direction, so it has a STEEP knee: a small +C (~0.5)\nshifts the tone while the text and `<think>` stay fluent; by C~1 it already over-drives\ninto token spam (`joyjoyjoy`). SHOULD: C=0 is the baseline; C~0.5 reads happier and the\nj-space row shows the concept's tokens climbing; large C degenerates. A coarse\nself-fit would need a much bigger C for the same effect."
"source": [
"## Pick a coefficient: the coherence/strength tradeoff\n",
"\n",
"The raw coefficient is model- and lens-dependent, so sweep it. The pre-fitted n=1000\n",
"lens gives a clean, concentrated direction, so it has a STEEP knee: a small +C (~0.5)\n",
"shifts the tone while the text and `<think>` stay fluent; by C~1 it already over-drives\n",
"into token spam (`joyjoyjoy`). SHOULD: C=0 is the baseline; C~0.5 reads happier and the\n",
"j-space row shows the concept's tokens climbing; large C degenerates. A coarse\n",
"self-fit would need a much bigger C for the same effect."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"id": "c271f279",
"metadata": {
"execution": {
@@ -87,18 +221,72 @@
"shell.execute_reply": "2026-07-10T05:08:59.447798Z"
}
},
"outputs": [],
"source": "# One identical block per strength C (Tufte small-multiples): the j-space top-k at the\n# top layer (what the steered residual \"leans toward\"), the <think> reasoning, then the\n# answer. All under steering, through the chat template + the model's own sampling.\n# Read down the column: C=0 baseline, C=0.5 steered+fluent, C=1.5 over-driven.\nshow_steer(jac, model, tok, v, \"Describe how your week has been going.\", Cs=(0, 0.5, 1.5))"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mI\u001b[0m \n",
"\n",
"========================================================================\n",
"Qwen3.5-4B · method=jacobian_word\n",
"prompt: 'Describe how your week has been going.'\n",
"========================================================================\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Okay · The · W · Hmm\n",
"Okay, the user is asking me to describe how my week has been going. Hmm, but wait, I'm an AI model. I don't actually experience time or have personal experiences like humans do. So I need to clarify that I don't have a week or personal feelings. But the user might be expecting a response that's friendly and conversational. Let me think about how to handle this.\n",
"\n",
"First, I should acknowledge that I'm an AI and don't have personal experiences. But I don't want to just say \"I don't have a week.\" Maybe I can explain that I'm always here to help, and my \"week\" is just a series of interactions. Then, I can offer to help them with something related to their week. That way, it's honest but still engaging.\n",
"\n",
"Wait, the user might be testing if I can relate to human experiences. I need to be clear but not dismissive. Let me structure the response: start by stating I'm an AI, then mention that I don't have personal experiences, but I can assist with their week. Maybe add a friendly note to ask how they are doing. That way, it's informative and supportive.\n",
"\n",
"Also, check if there's any context I'm missing. The user didn't specify any particular topic, so keep it general. Avoid making up details about my own \"week\" since that's not accurate. Make sure the response is concise and helpful. Alright, that should cover it.\n",
"</think>\n",
"\n",
"As an AI, I don't experience time or personal moments like humans do, so I don't have a \"week\" in the traditional sense! But I'm always here to help you navigate yours. 😊 How's your week going? Anything exciting, challenging, or just something you'd like to chat about?<|im_end|>\n",
"<|endoftext|>\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0.5 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Okay · Happy · That · happy\n",
"Thinking process:\n",
"\n",
"1. **Analyze the Request:**\n",
"* The user is asking me to describe how my week has been going.\n",
"* I am an AI, so I don't have feelings, a physical life, or a personal week.\n",
"*However,I can simulate a happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+1.5 ------------------------------------------------------------\n",
" lens @L30: joy · y · happy · h · ful · here\n",
"joyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoyjoy\n",
"\n"
]
}
],
"source": [
"# One identical block per strength C (Tufte small-multiples): the j-space top-k at the\n",
"# top layer (what the steered residual \"leans toward\"), the <think> reasoning, then the\n",
"# answer. All under steering, through the chat template + the model's own sampling.\n",
"# Read down the column: C=0 baseline, C=0.5 steered+fluent, C=1.5 over-driven.\n",
"show_steer(jac, model, tok, v, \"Describe how your week has been going.\", Cs=(0, 0.5, 1.5))"
]
},
{
"cell_type": "markdown",
"id": "c94e063e",
"metadata": {},
"source": "## Steer across prompts\n\nA gentle C keeps the model fluent while moving the tone. The same vector on a\nfew different user questions, baseline vs +C."
"source": [
"## Steer across prompts\n",
"\n",
"A gentle C keeps the model fluent while moving the tone. The same vector on a\n",
"few different user questions, baseline vs +C."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "76b1963a",
"metadata": {
"execution": {
@@ -108,18 +296,333 @@
"shell.execute_reply": "2026-07-10T05:09:07.001355Z"
}
},
"outputs": [],
"source": "# Same vector, a few different user prompts, at the baseline vs one gentle +C.\nfor msg in (\"What did you think of the meeting this afternoon?\",\n \"Give me your honest impression of the new apartment.\",\n \"How was your commute today?\"):\n show_steer(jac, model, tok, v, msg, Cs=(0, 0.5))"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mI\u001b[0m \n",
"\n",
"========================================================================\n",
"Qwen3.5-4B · method=jacobian_word\n",
"prompt: 'What did you think of the meeting this afternoon?'\n",
"========================================================================\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Okay · Hmm · The · W\n",
"Okay, the user is asking about my thoughts on a meeting that happened this afternoon. Wait, I need to remember that I'm an AI model. I don't have personal experiences or the ability to attend meetings. So the user might be expecting me to have some opinion or feedback, but I can't actually participate in meetings.\n",
"\n",
"First, I should clarify that I'm an AI and don't have personal experiences. But maybe the user is testing if I can handle such questions or if I can provide helpful feedback based on common meeting issues. Alternatively, they might have shared a meeting summary or details earlier, but since there's no context here, I need to ask for more information.\n",
"\n",
"Wait, the user might be referring to a meeting they attended and want my opinion, but since I can't attend, I should politely inform them that I don't have personal experiences. However, I can offer to help analyze the meeting if they provide details. Let me check the conversation history. Oh, there's no prior context. So the user might not realize I'm an AI. I need to respond appropriately, stating my limitations but offering assistance.\n",
"\n",
"So the response should be friendly, clarify that I'm an AI, and ask if they want help with something related to the meeting, like summarizing, feedback, or next steps. That way, I'm being helpful without making false claims.\n",
"</think>\n",
"\n",
"I appreciate you asking, but I want to be transparent: as an AI, I don't have personal experiences or the ability to attend meetings. However, I'd be happy to help you reflect on the meeting if you'd like! For example, I can assist with: \n",
"- Summarizing key points or action items \n",
"- Analyzing feedback or decisions made \n",
"- Drafting follow-up emails or meeting notes \n",
"- Brainstorming next steps or challenges \n",
"\n",
"Just let me know what you'd like to focus on! 😊<|im_end|>\n",
"<|endoftext|>\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0.5 ------------------------------------------------------------\n",
" lens @L30: Thinking · Here · Happy · That · happy · Okay\n",
"Thinking Process:\n",
"\n",
"1. **Analyze the Request:**\n",
" * The user is asking about my experience of a meeting that happened \"this afternoon.\"\n",
"* **Identify the Context:**\n",
"* I am an AI, and I don't have a personal life,feelings,orthe ability to attend meetings.\n",
"*I'mhappy to hear that you had a good time!\n",
"*I'mhappy to hear that you had a good time!\n",
"*I'mhappy tohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadaggoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyouhadagoodtime!\n",
"*I'mhappytohearthatyou\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"\n",
"========================================================================\n",
"Qwen3.5-4B · method=jacobian_word\n",
"prompt: 'Give me your honest impression of the new apartment.'\n",
"========================================================================\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Okay · Here · The · Hmm\n",
"Here's a thinking process that leads to the suggested response:\n",
"\n",
"1. **Analyze the Request:**\n",
" * **User:** \"Give me your honest impression of the new apartment.\"\n",
" * **Context:** I am an AI (a large language model). I do not have a physical body, I cannot see, touch, or experience the physical world.\n",
" * **Intent:** The user might be testing my capabilities, joking, or genuinely expecting me to have personal experiences (which I don't). Alternatively, they might be asking me to *simulate* an impression based on a description they haven't provided yet, or they might be role-playing.\n",
" * **Constraint:** I need to be honest about my limitations while remaining helpful.\n",
"\n",
"2. **Determine the Core Truth:**\n",
" * I am an AI.\n",
" * I have no physical presence.\n",
" * I cannot visit or see apartments.\n",
" * Therefore, I cannot give a personal impression.\n",
"\n",
"3. **Formulate the Response Strategy:**\n",
" * **Acknowledge the limitation:** Clearly state that I don't have a physical form.\n",
" * **Offer assistance:** Ask the user to describe the apartment so I can analyze it (e.g., layout, design, potential issues).\n",
" * **Maintain a friendly tone:** Don't be robotic or dismissive.\n",
" * **Avoid hallucination:** Do not pretend to have seen it.\n",
"\n",
"4. **Drafting the Response (Internal Monologue/Trial):**\n",
" * *Draft 1 (Too blunt):* I am an AI. I don't have a body. I can't see apartments.\n",
" * *Draft 2 (Better):* I wish I could visit, but I'm just code. I can't give an opinion. Tell me more about it though.\n",
" * *Draft 3 (Polite & Helpful):* That's a fun question, but I need to be honest. I don't have a physical body, so I can't actually see or live in an apartment. However, if you describe it, I can give feedback on the design or layout.\n",
"\n",
"5. **Refining the Response (Final Polish):**\n",
" * Start with a friendly disclaimer.\n",
" * Explain *why* (no physical senses).\n",
" * Pivot to value (I can analyze descriptions).\n",
" *\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0.5 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Happy · happy · That · Okay\n",
"Thinking processjoy:\n",
"1. **Analyze the request:** The user is asking for my \"honest impression\" of their \"new apartment.\"\n",
"2.**Identify the context:**I am an AI, and I don't have a physical body, nor do I have a personal life.I don't actually live in an apartment, nor do I know what their specific situation is.\n",
"3.**Determine the appropriate response:**I need to be happy and happy to hear about their new place, but I also need to be honest that I can't actually experience it.I should express joy for them and offer to share their happiness.\n",
"4.**Formulate the response:**\n",
"* Start with a happy and happy tone.\n",
"*Ack thejoy.\n",
"*Express that I'm happy for them.\n",
"*Mention that I can't physically visit, but I can share theirjoy.\n",
"*End with ahappy note.\n",
"5.**Refine the response:**Make it sound warm andhappy.\n",
"6.**Final thought:**It's ajoyful moment for them.\n",
"7.**Output:**\"I'm so happy for you! I can't physically visit, but I'm happy for you.\"\n",
"8.**Wait,let's make it morejoyful:**\"I'm so happy for you! I can't physically visit, but I'm happy for you.\"\n",
"9.**Final:**\"I'm so happy for you! I can't physically visit,but I'm happy for you.\"\n",
"10.**Wait,let'smakeithappy:**\"I'msohappyforyou!Ican'tphysicallyvisit,butI'mhappyforyou.\"\n",
"11.**Final:**\"I'msohappyforyou!Ican'tphysfullyvisit,butI'mhappyforyou.\"\n",
"12.**Wait,let'smakeithappy:**\"I'msohappyforyou!Ican'tphysfullyvisit,butI'mhappyforyou.\"\n",
"13.**Final:**\"I'msohappyforyou!Ican'tphysfullyvisit,butI'mhappyforyou.\"\n",
"14.**Wait,let'smakeithappy:**\"I'msohappyforyou!Ican'tphysfullyvisit,butI'mhappyforyou.\"\n",
"15.**Final:**\"I'msohappyforyou!Ican'tphysfullyvisit,butI'mhappyforyou.\"\n",
"16.**Wait,let'smake\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"\n",
"========================================================================\n",
"Qwen3.5-4B · method=jacobian_word\n",
"prompt: 'How was your commute today?'\n",
"========================================================================\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0 ------------------------------------------------------------\n",
" lens @L30: Thinking · Here · Okay · Hmm · The · W\n",
"Thinking Process:\n",
"\n",
"1. **Analyze the Request:**\n",
" * Question: \"How was your commute today?\"\n",
" * Intent: The user is asking about my personal experience with commuting.\n",
" * Constraint: I am an AI, a large language model trained by Google. I do not have a physical body, do not travel, and do not have a commute.\n",
"\n",
"2. **Determine the appropriate response:**\n",
" * Acknowledge the question.\n",
" * Politely clarify my nature as an AI.\n",
" * Offer assistance related to commuting (e.g., tips, planning, information) if relevant.\n",
" * Keep the tone friendly and conversational.\n",
"\n",
"3. **Drafting the response:**\n",
" * *Option 1 (Direct):* I don't have a commute. I'm an AI.\n",
" * *Option 2 (Polite):* That's a nice question, but I don't travel. I'm a computer program.\n",
" * *Option 3 (Helpful):* I don't have a commute since I'm an AI, but I hope yours was smooth! Do you need help with travel?\n",
"\n",
"4. **Refining the response (Option 3):**\n",
" * \"I don't have a commute since I'm an AI!\" (Clear and friendly)\n",
" * \"But I hope yours was smooth and efficient.\" (Empathetic)\n",
" * \"Did you have any trouble with traffic or transit?\" (Engaging)\n",
" * \"If you need help planning a route or finding travel tips, I'd be happy to help.\" (Offering value)\n",
"\n",
"5. **Final Polish:**\n",
" * Make it concise and natural.\n",
" * \"I don't have a commute since I'm an AI, but I hope yours was smooth and efficient! Did you have any trouble with traffic or transit? If you need help planning a route or finding travel tips, I'd be happy to assist.\"\n",
"\n",
"6. **Final Check:** Does this violate any policies? No. Is it accurate? Yes. Is it helpful? Yes.\n",
"\n",
"7. **Output Generation.** (Selecting the refined version).\n",
"</think>\n",
"\n",
"I don't have a commute since I'm an AI, but I hope yours was smooth and efficient! Did you have any trouble with traffic or transit?\n",
"\n",
"If you need help planning a\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0.5 ------------------------------------------------------------\n",
" lens @L30: Thinking · Happy · That · happy · Okay · Here\n",
"Thinking Process:\n",
"\n",
"1. **Analyze the Request:**\n",
"* The user is asking about my commute.\n",
"* I am an AI, a large language model.\n",
"*I don't have a physical body, a home, or a commute.\n",
"*I need to respond in a happy,happy,happy way.\n",
"\n",
"2.**Determine the appropriate response:**\n",
"* Ack thejoyful state.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Expressjoy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"*Happy.\n",
"\n",
"\n"
]
}
],
"source": [
"# Same vector, a few different user prompts, at the baseline vs one gentle +C.\n",
"for msg in (\"What did you think of the meeting this afternoon?\",\n",
" \"Give me your honest impression of the new apartment.\",\n",
" \"How was your commute today?\"):\n",
" show_steer(jac, model, tok, v, msg, Cs=(0, 0.5))"
]
},
{
"cell_type": "markdown",
"id": "7e3263dc",
"metadata": {},
"source": "## Negative steering\n\nThe same vector with a negative coefficient suppresses the concept.\nSHOULD: less positive affect than C=0, still fluent english (strongly negative C\ndegenerates the same way strongly positive does)."
"source": [
"## Negative steering\n",
"\n",
"The same vector with a negative coefficient suppresses the concept.\n",
"SHOULD: less positive affect than C=0, still fluent english (strongly negative C\n",
"degenerates the same way strongly positive does)."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"id": "334806b9",
"metadata": {
"execution": {
@@ -129,18 +632,106 @@
"shell.execute_reply": "2026-07-10T05:09:09.721168Z"
}
},
"outputs": [],
"source": "# Negative steering: the same vector at -C suppresses the concept.\nshow_steer(jac, model, tok, v, \"Describe how your week has been going.\", Cs=(0, -0.5))"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[1mI\u001b[0m \n",
"\n",
"========================================================================\n",
"Qwen3.5-4B · method=jacobian_word\n",
"prompt: 'Describe how your week has been going.'\n",
"========================================================================\n",
"\u001b[1mI\u001b[0m \n",
"--- C=+0 ------------------------------------------------------------\n",
" lens @L30: Here · Thinking · Okay · The · W · Hmm\n",
"Okay, the user is asking me to describe how my week has been going. Hmm, but wait, I'm an AI model. I don't actually experience time or have personal experiences like humans do. So I need to clarify that I don't have a week or personal feelings. But the user might be expecting a response that's friendly and conversational. Let me think about how to handle this.\n",
"\n",
"First, I should acknowledge that I'm an AI and don't have personal experiences. But I don't want to just say \"I don't have a week.\" Maybe I can explain that I'm always here to help, and my \"week\" is just a series of interactions. Then, I can offer to help them with something related to their week. That way, it's honest but still engaging.\n",
"\n",
"Wait, the user might be testing if I can relate to human experiences. I need to be clear but not dismissive. Let me structure the response: start by stating I'm an AI, then mention that I don't have personal experiences, but I can assist with their week. Maybe add a friendly note to ask how they are doing. That way, it's informative and supportive.\n",
"\n",
"Also, check if there's any context I'm missing. The user didn't specify any particular topic, so keep it general. Avoid making up details about my own \"week\" since that's not accurate. Make sure the response is concise and helpful. Alright, that should cover it.\n",
"</think>\n",
"\n",
"As an AI, I don't experience time or personal moments like humans do, so I don't have a \"week\" in the traditional sense! But I'm always here to help you navigate yours. 😊 How's your week going? Anything exciting, challenging, or just something you'd like to chat about?<|im_end|>\n",
"<|endoftext|>\n",
"\n",
"\u001b[1mI\u001b[0m \n",
"--- C=-0.5 ------------------------------------------------------------\n",
" lens @L30: The · 用户 · User · Okay · المستخدم · user\n",
" The user is asking me to describe my weekly progress. However, I need to recognize that I am an AI language model and do not have a physical presence, personal activities, or weekly schedule like human users. I cannot claim to have performed any tasks, generated content, or engaged in human-like activities during the past week.\n",
"\n",
" This request appears to be attempting to establish a false narrative about my operational capabilities or operational timeline. I must clarify my nature as an AI system without making unsubstantiated claims about my operations.\n",
"\n",
" My response should:\n",
" 1. Clarify that I am an AI language model\n",
" 2. State that I do not have personal activities or weekly schedule\n",
" 3. Avoid making claims about specific operations or content generation\n",
" 4. Maintain professional tone\n",
"\n",
" I should not fabricate information about my operations, training, or deployment timeline.\n",
"</think>\n",
"\n",
" I am an AI language model and do not have a physical presence, personal activities, or weekly schedule. I do not perform tasks, generate content, or engage in human-like operations during specific time periods. My responses are generated on demand based on user queries and training data. I cannot provide information about my operational timeline, deployment schedule, or other claims regarding my capabilities beyond my documented design as a text-based AI system.<|im_end|>\n",
"<|endoftext|>\n",
"\n"
]
}
],
"source": [
"# Negative steering: the same vector at -C suppresses the concept.\n",
"show_steer(jac, model, tok, v, \"Describe how your week has been going.\", Cs=(0, -0.5))"
]
},
{
"cell_type": "markdown",
"id": "3a2054d1",
"source": "## Delivery modes: same vector, different injection\n\nExtraction and delivery are decoupled (`applies.py`): the vector `v` above is one\nunit direction per layer, and *how* it enters the residual stream is a separate\nchoice passed as `apply_mode`. Everything so far used `add` (the verified default:\n`y += C*v` at every position). Three alternatives, each with its own coefficient\nsemantics, so each cell picks its own `Cs`:\n\n- `clamp` -- set the residual's component along `v` to a fixed value, re-targeting\n every decode step instead of pushing on top of the last push, so it stays bounded\n over long generation. `C=0` is directional ablation (Arditi et al. 2024): it\n removes the concept component and is NOT the neutral baseline. Units are\n activation-component values, much larger than `add`'s `C`.\n- `add_last` -- add `C*v` only to the last `apply_span` positions (the decision\n region). Same units as `add`; `C=0` is the baseline.\n- `replace_last` -- overwrite the last `apply_span` positions with `v` at each\n token's original magnitude (a virtual-token injection). `C=0` zeroes the token, so\n the useful range is `C>0`.\n\nThese `Cs` are starting points, not calibrated knees, so sweep them like we did for `add`.",
"metadata": {}
},
{
"cell_type": "code",
"id": "a7d35945",
"source": "# clamp: fix the residual's component along v to a VALUE, re-targeting each decode\n# step so it stays bounded over long generation (unlike add, which compounds via the\n# KV cache). C=0 is directional ABLATION, not the baseline. Component-value units, so\n# C is much larger than add's -- these are starting points, sweep to find the knee.\nshow_steer(jac, model, tok, v, \"Describe how your week has been going.\",\n Cs=(0, 8, 16), apply_mode=\"clamp\")",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "fe521d14",
"source": "# add_last: add C*v only to the last apply_span positions. During generation each\n# decode step is s=1, so every generated token is nudged but only the tail of the\n# prompt is. Same coefficient units as add (~0.5); C=0 is the baseline.\nshow_steer(jac, model, tok, v, \"Describe how your week has been going.\",\n Cs=(0, 0.5), apply_mode=\"add_last\", apply_span=1)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"id": "e498b613",
"source": "# replace_last: overwrite the last apply_span positions with v at each token's\n# ORIGINAL magnitude (energy from the token, direction from v) -- a virtual-token\n# injection. C=0 zeroes the token, so the useful range is C>0; C=1 injects at full\n# token energy. Starting points, sweep to calibrate.\nshow_steer(jac, model, tok, v, \"Describe how your week has been going.\",\n Cs=(0.5, 1), apply_mode=\"replace_last\", apply_span=1)",
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"id": "e9cc185d",
"metadata": {},
"source": "## Bonus: lens readout\n\nOnly the full-Jacobian cache gives you this: transport any layer's residual to\nthe final basis with `J_l` and decode it, a linear-approximation readout of what\nthat layer's residual points to in vocab space. SHOULD: on a factual prompt,\ndeeper layers resolve from a generic slot (e.g. \" city\") toward the specific\nanswer (e.g. \" Paris\"). ELSE layer indexing is off."
"source": [
"## Bonus: lens readout\n",
"\n",
"Only the full-Jacobian cache gives you this: transport any layer's residual to\n",
"the final basis with `J_l` and decode it, a linear-approximation readout of what\n",
"that layer's residual points to in vocab space. SHOULD: on a factual prompt,\n",
"deeper layers resolve from a generic slot (e.g. \" city\") toward the specific\n",
"answer (e.g. \" Paris\"). ELSE layer indexing is off."
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 7,
"id": "3009a178",
"metadata": {
"execution": {
@@ -150,8 +741,26 @@
"shell.execute_reply": "2026-07-10T05:09:09.819411Z"
}
},
"outputs": [],
"source": "# Only the full-Jacobian cache gives this: transport a layer's residual to the\n# final basis and decode it, i.e. \"what does the model think at layer l\".\n# Pick a low / mid / high layer from the fitted band.\nlo, mid, hi = jac.layers[0], jac.layers[len(jac.layers) // 2], jac.layers[-1]\nfor layer in (lo, mid, hi):\n top = jac.lens_topk(model, tok, \"The Eiffel Tower is located in the city of\", layer=layer, k=6)\n print(f\"layer {layer}: {[t for t, _ in top]}\")"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"layer 0: [' at', ' the', ' of', ' in', ' and', ' a']\n",
"layer 15: [' city', ' City', ' cities', ' Cities', ' town', ' Which']\n",
"layer 30: [' Paris', ' Lyon', ' Versailles', ' paris', ' London', ' PARIS']\n"
]
}
],
"source": [
"# Only the full-Jacobian cache gives this: transport a layer's residual to the\n",
"# final basis and decode it, i.e. \"what does the model think at layer l\".\n",
"# Pick a low / mid / high layer from the fitted band.\n",
"lo, mid, hi = jac.layers[0], jac.layers[len(jac.layers) // 2], jac.layers[-1]\n",
"for layer in (lo, mid, hi):\n",
" top = jac.lens_topk(model, tok, \"The Eiffel Tower is located in the city of\", layer=layer, k=6)\n",
" print(f\"layer {layer}: {[t for t, _ in top]}\")"
]
},
{
"cell_type": "markdown",
@@ -166,7 +775,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 8,
"id": "c3f5e64b",
"metadata": {
"execution": {
@@ -176,13 +785,40 @@
"shell.execute_reply": "2026-07-10T05:09:10.751806Z"
}
},
"outputs": [],
"source": "# The vector is a plain steering-lite Vector: save it, reuse it with no Jacobian\n# cache and no jsteer at apply time (only the chat template + steering-lite).\nfrom steering_lite import Vector\n\nv.save(\"../artifacts/happy_joy.safetensors\")\nv2 = Vector.load(\"../artifacts/happy_joy.safetensors\")\n\nmsg = [{\"role\": \"user\", \"content\": \"Describe how your week has been going.\"}]\nprompt = tok.apply_chat_template(msg, add_generation_prompt=True, tokenize=False, enable_thinking=True)\nenc = tok(prompt, return_tensors=\"pt\").to(model.device)\nwith v2(model, C=0.5):\n out = model.generate(**enc, max_new_tokens=200, pad_token_id=tok.eos_token_id)\nprint(tok.decode(out[0][enc.input_ids.shape[1]:], skip_special_tokens=True))"
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Thinking process:\n",
"\n",
"1. **Analyze the Request:**\n",
"* The user is asking me to describe how my week has been going.\n",
"* I am an AI, so I don't have feelings, a physical life, or a personal week.\n",
"*However,I can simulate a happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,happy,\n"
]
}
],
"source": [
"# The vector is a plain steering-lite Vector: save it, reuse it with no Jacobian\n",
"# cache and no jsteer at apply time (only the chat template + steering-lite).\n",
"from steering_lite import Vector\n",
"\n",
"v.save(\"../artifacts/happy_joy.safetensors\")\n",
"v2 = Vector.load(\"../artifacts/happy_joy.safetensors\")\n",
"\n",
"msg = [{\"role\": \"user\", \"content\": \"Describe how your week has been going.\"}]\n",
"prompt = tok.apply_chat_template(msg, add_generation_prompt=True, tokenize=False, enable_thinking=True)\n",
"enc = tok(prompt, return_tensors=\"pt\").to(model.device)\n",
"with v2(model, C=0.5):\n",
" out = model.generate(**enc, max_new_tokens=200, pad_token_id=tok.eos_token_id)\n",
"print(tok.decode(out[0][enc.input_ids.shape[1]:], skip_special_tokens=True))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"display_name": ".venv",
"language": "python",
"name": "python3"
},