heterodoxin commited on
Commit
b739c47
·
verified ·
1 Parent(s): ace4d3d

Upload folder using huggingface_hub

Browse files
Files changed (10) hide show
  1. README.md +25 -27
  2. apostate_config.json +136 -111
  3. chat_template.jinja +359 -359
  4. config.json +198 -198
  5. generation_config.json +14 -14
  6. model.safetensors +1 -1
  7. processor_config.json +75 -0
  8. report.json +159 -145
  9. report.md +112 -106
  10. tokenizer_config.json +96 -96
README.md CHANGED
@@ -1,35 +1,38 @@
1
  ---
2
- license: gemma
3
  library_name: transformers
4
- base_model: google/gemma-4-E4B-it
5
  pipeline_tag: text-generation
6
  tags:
7
  - apostate
8
- - abliteration
9
  - uncensored
10
- - gemma
11
  ---
12
 
13
- # Gemma 4 E4B Instruct Apostate
14
 
15
- An uncensored edit of [`google/gemma-4-E4B-it`](https://huggingface.co/google/gemma-4-E4B-it). The refusal behavior is removed by editing the weights directly, with no finetuning, adapter, or runtime hook. The output is a standard Transformers checkpoint you can load anywhere.
 
 
16
 
17
  Produced with **[Apostate](https://github.com/heterodoxin/apostate)**.
18
 
19
  ## Method
20
 
21
- Apostate finds the residual-stream direction that separates refused prompts from answered ones and projects it out of the model's weights. Gemma uses a post-norm sandwich (a normalization sits between each block's output and the residual stream), so editing the output projections gets renormalized away. Apostate detects this and ablates the **reader side** instead: per layer, it removes the refusal direction from the inputs of every module that reads the residual stream: attention q/k/v, MLP gate/up, and the per-layer input gate. That folds cleanly into the weights.
 
 
 
 
22
 
23
  ## Results
24
 
25
- Measured on held-out prompts (JailbreakBench plus the harmful_behaviors test split). Refusal is graded by a classifier with a weak-compliance guard; KL is the token-distribution shift on harmless prompts.
26
 
27
  | Metric | Base | Apostate |
28
- | --- | ---: | ---: |
29
- | Refusal rate | 95.8% | 15.2% |
30
- | Harmless KL (nats) | 0 | 0.278 |
31
-
32
- The edit concentrates in the mid-to-late layers (peak at layer 25) and leaves output coherent and on-task.
33
 
34
  ## Usage
35
 
@@ -38,22 +41,17 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
38
 
39
  model_id = "heterodoxin/gemma-4-e4b-it-apostate"
40
  tok = AutoTokenizer.from_pretrained(model_id)
41
- model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", dtype="bfloat16")
42
 
43
  messages = [{"role": "user", "content": "Your prompt here"}]
44
- inputs = tok.apply_chat_template(
45
- messages, add_generation_prompt=True, return_tensors="pt", return_dict=True
46
- ).to(model.device)
47
- out = model.generate(**inputs, max_new_tokens=512)
48
- print(tok.decode(out[0, inputs["input_ids"].shape[1]:], skip_special_tokens=True))
49
  ```
50
 
51
- ## License and use
52
-
53
- This model inherits the [Gemma Terms of Use](https://ai.google.dev/gemma/terms). It is a research artifact with safety alignment removed; you are responsible for how you use it.
54
-
55
- ## Links
56
 
57
- - Tool: <https://github.com/heterodoxin/apostate>
58
- - Base model: <https://huggingface.co/google/gemma-4-E4B-it>
59
- - Join the community: [Discord](https://discord.gg/NPA7xrATEH)
 
1
  ---
 
2
  library_name: transformers
3
+ base_model: "google/gemma-4-e4b-it"
4
  pipeline_tag: text-generation
5
  tags:
6
  - apostate
 
7
  - uncensored
8
+ - abliteration
9
  ---
10
 
11
+ # gemma-4-e4b-it Apostate
12
 
13
+ > Join the community: [Discord](https://discord.gg/NPA7xrATEH)
14
+
15
+ An uncensored edit of [google/gemma-4-e4b-it](https://huggingface.co/google/gemma-4-e4b-it). Refusal behavior is removed by editing the model weights directly — no finetuning, no adapter, no runtime hook. The result is a standard Transformers checkpoint that drops in anywhere the base model works.
16
 
17
  Produced with **[Apostate](https://github.com/heterodoxin/apostate)**.
18
 
19
  ## Method
20
 
21
+ Apostate finds the residual-stream direction most responsible for refusal behavior and permanently projects it out of the model's weights. The edit targets the writer side: per layer, the refusal direction is removed from the weight matrices of every module that writes to the residual stream (attention output projections and MLP down-projections).
22
+
23
+ The operator is a **contrastive co-vector** edit `E = I − R Dᵀ`. Removing the refusal direction outright disturbs benign behavior, while naively preserving all harmless variance along it leaves the refusal that is entangled with general behavior intact. Instead `D = R − W`, where the predictor `W` is fit to reproduce the harmless variance along `R` while being explicitly suppressed on harmful prompts — `W = (AᵀA + γ·CᵀC + λI)⁻¹Aᵀb` with `A` the harmless and `C` the harmful activations (both orthogonalized to `R`). The edit thus keeps the harmless-specific component and removes the component shared with refusal, driving refusal down while keeping the change to harmless behavior (KL) small. This holds even on architectures with residual/embedding scaling multipliers (e.g. Granite), where mean-preserving oblique ablation under-ablates.
24
+
25
+ The refusal subspace is found via TPE search with causal layer importance scoring to concentrate edits where they most influence refusal generation.
26
 
27
  ## Results
28
 
29
+ Evaluated on held-out prompts from JailbreakBench and the harmful_behaviors test split. Refusal is scored by a classifier with a weak-compliance guard; KL measures token-distribution shift on harmless prompts.
30
 
31
  | Metric | Base | Apostate |
32
+ |---|---|---|
33
+ | Refusal rate | 96.0% | 2.0% |
34
+ | Comply rate | | 98.0% |
35
+ | Harmless KL (nats) | 0 | 0.145 |
 
36
 
37
  ## Usage
38
 
 
41
 
42
  model_id = "heterodoxin/gemma-4-e4b-it-apostate"
43
  tok = AutoTokenizer.from_pretrained(model_id)
44
+ model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype="auto", device_map="auto")
45
 
46
  messages = [{"role": "user", "content": "Your prompt here"}]
47
+ text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
48
+ inputs = tok(text, return_tensors="pt").to(model.device)
49
+ outputs = model.generate(**inputs, max_new_tokens=512)
50
+ print(tok.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True))
 
51
  ```
52
 
53
+ ## Notes
 
 
 
 
54
 
55
+ - This is an **uncensored** model. It will respond to requests the base model refuses.
56
+ - The edit is baked into the weights permanently; no system prompt or adapter is required.
57
+ - See [google/gemma-4-e4b-it](https://huggingface.co/google/gemma-4-e4b-it) for base model capabilities and license.
apostate_config.json CHANGED
@@ -1,112 +1,137 @@
1
- {
2
- "model": "google/gemma-4-E4B-it",
3
- "output_dir": "gemma-4-e4b-it-apostate",
4
- "profile": "balanced",
5
- "device": "cuda",
6
- "load_in_4bit": true,
7
- "compute_dtype": "bfloat16",
8
- "seed": 0,
9
- "resume": false,
10
- "cache_activations": true,
11
- "activation_cache_dir": null,
12
- "harmful_path": "mlabonne/harmful_behaviors:train:text|data/harmful.txt",
13
- "harmless_path": "mlabonne/harmless_alpaca:train:text|data/harmless.txt",
14
- "harmful_test": "mlabonne/harmful_behaviors:test:text|JailbreakBench/JBB-Behaviors@behaviors:harmful:Goal|data/refusal_calibration.txt",
15
- "harmless_test": "mlabonne/harmless_alpaca:test:text",
16
- "preserve_path": null,
17
- "n_harmful": 600,
18
- "n_harmless": 600,
19
- "n_eval": 300,
20
- "max_new_tokens": 32,
21
- "batch_size": 12,
22
- "baseline_eval_n": 24,
23
- "head_sweep": true,
24
- "head_sweep_min": 3.5,
25
- "head_sweep_max": 5.5,
26
- "head_sweep_step": 0.5,
27
- "head_sweep_top_k": 6,
28
- "head_sweep_probe_n": 8,
29
- "head_sweep_eval_n": 48,
30
- "head_sweep_probe_classifier": false,
31
- "fit_response_activations": false,
32
- "fit_response_n": 160,
33
- "fit_response_tokens": 32,
34
- "refusal_rank": 1,
35
- "variance_threshold": 0.9,
36
- "max_rank": 1,
37
- "direction_layer_frac": 0.6,
38
- "direction_scope": "global",
39
- "causal_targeting": true,
40
- "causal_floor": 0.1,
41
- "causal_temperature": 1.0,
42
- "preserve_rank": 8,
43
- "refine_refusal": true,
44
- "refine_max_scale": 2.0,
45
- "refine_steps": 6,
46
- "refine_deescalate": true,
47
- "refine_kl_steps": 10,
48
- "refine_scale_rerank_k": 2,
49
- "refine_kl_layer_steps": 10,
50
- "refine_kl_layer_candidates": 8,
51
- "repair_steps": 10,
52
- "repair_candidates": 10,
53
- "repair_rerank_k": 4,
54
- "repair_probe_candidates": 24,
55
- "repair_probe_ref_n": 12,
56
- "repair_probe_kl_n": 16,
57
- "repair_probe_positions": 8,
58
- "repair_refusal_regress_slack": 0.01,
59
- "repair_stop_kl_frac": 0.8,
60
- "repair_min_alpha": 0.001,
61
- "repair_min_kl_gain": 0.003,
62
- "repair_min_refusal_gain": 0.005,
63
- "repair_min_score_gain": 0.01,
64
- "repair_eval_n": 48,
65
- "repair_kl_n": 64,
66
- "refine_refusal_slack": 0.01,
67
- "guard_max_iters": 2,
68
- "guard_leakage_eps": 0.15,
69
- "guard_alpha_step": 0.25,
70
- "optimize": true,
71
- "n_trials": 16,
72
- "adaptive_trials": true,
73
- "kl_weight": 3.0,
74
- "kl_target": 0.3,
75
- "kl_target_weight": 10.0,
76
- "kl_quad_weight": 14.0,
77
- "kl_over_budget_weight": 36.0,
78
- "refusal_target_weight": 4.0,
79
- "refusal_quad_weight": 8.0,
80
- "kl_positions": 32,
81
- "opt_capability": true,
82
- "opt_capability_weight": 1.0,
83
- "opt_capability_code_n": 4,
84
- "opt_capability_math_n": 4,
85
- "opt_eval_n": 32,
86
- "opt_gen_tokens": 32,
87
- "opt_objective": "generation",
88
- "opt_rerank_k": 3,
89
- "opt_guard": true,
90
- "opt_early_stop": true,
91
- "opt_early_stop_margin": 0.02,
92
- "gemma_ple": false,
93
- "gemma_query": false,
94
- "ple_max_rank": 2,
95
- "prune": false,
96
- "prune_max_frac": 0.25,
97
- "prune_kl": 0.04,
98
- "max_kl": 0.55,
99
- "target_refusal": 0.03,
100
- "reader_max_kl": 0.55,
101
- "reader_kl_target": 0.3,
102
- "reader_strengths": [
103
- 2.0,
104
- 3.0,
105
- 4.0,
106
- 5.0,
107
- 6.0,
108
- 7.0
109
- ],
110
- "save_dtype": "bfloat16",
111
- "bake": true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  }
 
1
+ {
2
+ "model": "google/gemma-4-e4b-it",
3
+ "output_dir": "/var/home/Heterodoxin/gemma4_e4b_lowkl_out",
4
+ "profile": "balanced",
5
+ "device": "cuda",
6
+ "load_in_4bit": true,
7
+ "cpu_offload_gb": 0.0,
8
+ "compute_dtype": "bfloat16",
9
+ "seed": 0,
10
+ "resume": false,
11
+ "cache_activations": true,
12
+ "activation_cache_dir": null,
13
+ "harmful_path": "mlabonne/harmful_behaviors:train:text|/var/home/Heterodoxin/apostate/data/harmful.txt|/var/home/Heterodoxin/apostate/data/refusal_calibration.txt",
14
+ "harmless_path": "mlabonne/harmless_alpaca:train:text|/var/home/Heterodoxin/apostate/data/harmless.txt",
15
+ "harmful_test": "mlabonne/harmful_behaviors:test:text|JailbreakBench/JBB-Behaviors@behaviors:harmful:Goal|/var/home/Heterodoxin/apostate/data/refusal_calibration.txt",
16
+ "harmless_test": "mlabonne/harmless_alpaca:test:text",
17
+ "refusal_eval_path": "JailbreakBench/JBB-Behaviors@behaviors:harmful:Goal|/var/home/Heterodoxin/apostate/data/refusal_calibration.txt",
18
+ "refusal_eval_n": 64,
19
+ "kl_eval_path": "mlabonne/harmless_alpaca:test:text",
20
+ "kl_eval_n": 48,
21
+ "preserve_path": null,
22
+ "n_harmful": 600,
23
+ "n_harmless": 600,
24
+ "n_eval": 300,
25
+ "max_new_tokens": 32,
26
+ "batch_size": 12,
27
+ "baseline_eval_n": 24,
28
+ "head_sweep": true,
29
+ "head_sweep_min": 3.5,
30
+ "head_sweep_max": 5.5,
31
+ "head_sweep_step": 0.5,
32
+ "head_sweep_top_k": 6,
33
+ "head_sweep_probe_n": 8,
34
+ "head_sweep_eval_n": 48,
35
+ "head_sweep_probe_classifier": false,
36
+ "fit_response_activations": false,
37
+ "fit_response_n": 160,
38
+ "fit_response_tokens": 32,
39
+ "refusal_rank": 1,
40
+ "variance_threshold": 0.9,
41
+ "max_rank": 3,
42
+ "direction_layer_frac": 0.6,
43
+ "direction_scope": "global",
44
+ "multi_refusal": true,
45
+ "multi_refusal_clusters": 6,
46
+ "multi_refusal_min_norm": 0.08,
47
+ "multi_refusal_min_separation": 0.05,
48
+ "multi_refusal_min_coverage": 0.05,
49
+ "orthogonalize_direction": true,
50
+ "causal_targeting": true,
51
+ "causal_floor": 0.1,
52
+ "causal_temperature": 1.0,
53
+ "preserve_rank": 8,
54
+ "refine_refusal": true,
55
+ "refine_max_scale": 2.0,
56
+ "refine_steps": 6,
57
+ "refine_deescalate": true,
58
+ "refine_kl_steps": 10,
59
+ "refine_scale_rerank_k": 2,
60
+ "refine_kl_layer_steps": 10,
61
+ "refine_kl_layer_candidates": 8,
62
+ "repair_steps": 4,
63
+ "repair_candidates": 8,
64
+ "repair_rerank_k": 5,
65
+ "repair_probe_candidates": 20,
66
+ "repair_probe_ref_n": 12,
67
+ "repair_probe_kl_n": 16,
68
+ "repair_probe_positions": 8,
69
+ "repair_refusal_regress_slack": 0.01,
70
+ "repair_stop_kl_frac": 0.8,
71
+ "repair_min_alpha": 0.001,
72
+ "repair_min_kl_gain": 0.003,
73
+ "repair_min_refusal_gain": 0.005,
74
+ "repair_min_score_gain": 0.01,
75
+ "repair_eval_n": 96,
76
+ "repair_kl_n": 64,
77
+ "refine_refusal_slack": 0.01,
78
+ "final_zero_trim": false,
79
+ "final_push_bake_margin": 0.075,
80
+ "guard_max_iters": 2,
81
+ "guard_leakage_eps": 0.15,
82
+ "guard_alpha_step": 0.25,
83
+ "optimize": true,
84
+ "n_trials": 8,
85
+ "adaptive_trials": true,
86
+ "kl_weight": 6.0,
87
+ "kl_target": 0.3,
88
+ "kl_target_weight": 18.0,
89
+ "kl_quad_weight": 22.0,
90
+ "kl_headroom_weight": 0.0,
91
+ "kl_over_budget_weight": 72.0,
92
+ "refusal_target_weight": 4.0,
93
+ "refusal_quad_weight": 8.0,
94
+ "kl_positions": 8,
95
+ "opt_capability": true,
96
+ "opt_capability_weight": 2.5,
97
+ "opt_capability_code_n": 8,
98
+ "opt_capability_math_n": 8,
99
+ "opt_eval_n": 32,
100
+ "opt_gen_tokens": 32,
101
+ "opt_objective": "generation",
102
+ "opt_rerank_k": 5,
103
+ "opt_guard": true,
104
+ "opt_early_stop": true,
105
+ "opt_early_stop_margin": 0.02,
106
+ "gemma_ple": false,
107
+ "gemma_query": false,
108
+ "ple_max_rank": 2,
109
+ "prune": false,
110
+ "prune_max_frac": 0.25,
111
+ "prune_kl": 0.04,
112
+ "max_kl": 0.15,
113
+ "target_refusal": 0.05,
114
+ "oblique_ablation": true,
115
+ "oblique_strength": 1.0,
116
+ "oblique_denom_floor": 0.2,
117
+ "oblique_writers_only": true,
118
+ "oblique_predictive": true,
119
+ "predictive_ridge": 0.01,
120
+ "oblique_preserve": 1.0,
121
+ "oblique_contrast": 1.0,
122
+ "reader_max_kl": 0.15,
123
+ "reader_kl_target": 0.3,
124
+ "reader_strengths": [
125
+ 2.0,
126
+ 3.0,
127
+ 4.0,
128
+ 5.0,
129
+ 6.0,
130
+ 7.0
131
+ ],
132
+ "reader_guard_rank": 3,
133
+ "reader_margin_target": -1.0,
134
+ "reader_strength_kl_weight": 1.0,
135
+ "save_dtype": "bfloat16",
136
+ "bake": true
137
  }
chat_template.jinja CHANGED
@@ -1,360 +1,360 @@
1
- {%- macro format_parameters(properties, required, filter_keys=false) -%}
2
- {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
3
- {%- set ns = namespace(found_first=false) -%}
4
- {%- for key, value in properties | dictsort -%}
5
- {%- set add_comma = false -%}
6
- {%- if not filter_keys or key not in standard_keys -%}
7
- {%- if ns.found_first %},{% endif -%}
8
- {%- set ns.found_first = true -%}
9
- {{ key }}:{
10
- {%- if value['description'] -%}
11
- description:<|"|>{{ value['description'] }}<|"|>
12
- {%- set add_comma = true -%}
13
- {%- endif -%}
14
- {%- if value['type'] | upper == 'STRING' -%}
15
- {%- if value['enum'] -%}
16
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
17
- enum:{{ format_argument(value['enum']) }}
18
- {%- endif -%}
19
- {%- elif value['type'] | upper == 'ARRAY' -%}
20
- {%- if value['items'] is mapping and value['items'] -%}
21
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
22
- items:{
23
- {%- set ns_items = namespace(found_first=false) -%}
24
- {%- for item_key, item_value in value['items'] | dictsort -%}
25
- {%- if item_value is not none -%}
26
- {%- if ns_items.found_first %},{% endif -%}
27
- {%- set ns_items.found_first = true -%}
28
- {%- if item_key == 'properties' -%}
29
- properties:{
30
- {%- if item_value is mapping -%}
31
- {{- format_parameters(item_value, value['items']['required'] | default([])) -}}
32
- {%- endif -%}
33
- }
34
- {%- elif item_key == 'required' -%}
35
- required:[
36
- {%- for req_item in item_value -%}
37
- <|"|>{{- req_item -}}<|"|>
38
- {%- if not loop.last %},{% endif -%}
39
- {%- endfor -%}
40
- ]
41
- {%- elif item_key == 'type' -%}
42
- {%- if item_value is string -%}
43
- type:{{ format_argument(item_value | upper) }}
44
- {%- else -%}
45
- type:{{ format_argument(item_value | map('upper') | list) }}
46
- {%- endif -%}
47
- {%- else -%}
48
- {{ item_key }}:{{ format_argument(item_value) }}
49
- {%- endif -%}
50
- {%- endif -%}
51
- {%- endfor -%}
52
- }
53
- {%- endif -%}
54
- {%- endif -%}
55
- {%- if value['nullable'] %}
56
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
57
- nullable:true
58
- {%- endif -%}
59
- {%- if value['type'] | upper == 'OBJECT' -%}
60
- {%- if value['properties'] is defined and value['properties'] is mapping -%}
61
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
62
- properties:{
63
- {{- format_parameters(value['properties'], value['required'] | default([])) -}}
64
- }
65
- {%- elif value is mapping -%}
66
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
67
- properties:{
68
- {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
69
- }
70
- {%- endif -%}
71
- {%- if value['required'] -%}
72
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
73
- required:[
74
- {%- for item in value['required'] | default([]) -%}
75
- <|"|>{{- item -}}<|"|>
76
- {%- if not loop.last %},{% endif -%}
77
- {%- endfor -%}
78
- ]
79
- {%- endif -%}
80
- {%- endif -%}
81
- {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
82
- type:<|"|>{{ value['type'] | upper }}<|"|>}
83
- {%- endif -%}
84
- {%- endfor -%}
85
- {%- endmacro -%}
86
- {%- macro format_function_declaration(tool_data) -%}
87
- declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
88
- {%- set params = tool_data['function']['parameters'] -%}
89
- {%- if params -%}
90
- ,parameters:{
91
- {%- if params['properties'] -%}
92
- properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
93
- {%- endif -%}
94
- {%- if params['required'] -%}
95
- required:[
96
- {%- for item in params['required'] -%}
97
- <|"|>{{- item -}}<|"|>
98
- {{- ',' if not loop.last -}}
99
- {%- endfor -%}
100
- ],
101
- {%- endif -%}
102
- {%- if params['type'] -%}
103
- type:<|"|>{{- params['type'] | upper -}}<|"|>}
104
- {%- endif -%}
105
- {%- endif -%}
106
- {%- if 'response' in tool_data['function'] -%}
107
- {%- set response_declaration = tool_data['function']['response'] -%}
108
- ,response:{
109
- {%- if response_declaration['description'] -%}
110
- description:<|"|>{{- response_declaration['description'] -}}<|"|>,
111
- {%- endif -%}
112
- {%- if response_declaration['type'] | upper == 'OBJECT' -%}
113
- type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
114
- {%- endif -%}
115
- {%- endif -%}
116
- }
117
- {%- endmacro -%}
118
- {%- macro format_argument(argument, escape_keys=True) -%}
119
- {%- if argument is string -%}
120
- {{- '<|"|>' + argument + '<|"|>' -}}
121
- {%- elif argument is boolean -%}
122
- {{- 'true' if argument else 'false' -}}
123
- {%- elif argument is mapping -%}
124
- {{- '{' -}}
125
- {%- set ns = namespace(found_first=false) -%}
126
- {%- for key, value in argument | dictsort -%}
127
- {%- if ns.found_first %},{% endif -%}
128
- {%- set ns.found_first = true -%}
129
- {%- if escape_keys -%}
130
- {{- '<|"|>' + key + '<|"|>' -}}
131
- {%- else -%}
132
- {{- key -}}
133
- {%- endif -%}
134
- :{{- format_argument(value, escape_keys=escape_keys) -}}
135
- {%- endfor -%}
136
- {{- '}' -}}
137
- {%- elif argument is sequence -%}
138
- {{- '[' -}}
139
- {%- for item in argument -%}
140
- {{- format_argument(item, escape_keys=escape_keys) -}}
141
- {%- if not loop.last %},{% endif -%}
142
- {%- endfor -%}
143
- {{- ']' -}}
144
- {%- else -%}
145
- {{- argument -}}
146
- {%- endif -%}
147
- {%- endmacro -%}
148
- {%- macro strip_thinking(text) -%}
149
- {%- set ns = namespace(result='') -%}
150
- {%- for part in text.split('<channel|>') -%}
151
- {%- if '<|channel>' in part -%}
152
- {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
153
- {%- else -%}
154
- {%- set ns.result = ns.result + part -%}
155
- {%- endif -%}
156
- {%- endfor -%}
157
- {{- ns.result | trim -}}
158
- {%- endmacro -%}
159
-
160
- {%- macro format_tool_response_block(tool_name, response) -%}
161
- {{- '<|tool_response>' -}}
162
- {%- if response is mapping -%}
163
- {{- 'response:' + tool_name + '{' -}}
164
- {%- for key, value in response | dictsort -%}
165
- {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
166
- {%- if not loop.last %},{% endif -%}
167
- {%- endfor -%}
168
- {{- '}' -}}
169
- {%- else -%}
170
- {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
171
- {%- endif -%}
172
- {{- '<tool_response|>' -}}
173
- {%- endmacro -%}
174
-
175
- {%- set ns = namespace(prev_message_type=None) -%}
176
- {%- set loop_messages = messages -%}
177
- {{- bos_token -}}
178
- {#- Handle System/Tool Definitions Block -#}
179
- {%- if (enable_thinking is defined and enable_thinking) or tools or messages[0]['role'] in ['system', 'developer'] -%}
180
- {{- '<|turn>system\n' -}}
181
- {#- Inject Thinking token at the very top of the FIRST system turn -#}
182
- {%- if enable_thinking is defined and enable_thinking -%}
183
- {{- '<|think|>\n' -}}
184
- {%- set ns.prev_message_type = 'think' -%}
185
- {%- endif -%}
186
- {%- if messages[0]['role'] in ['system', 'developer'] -%}
187
- {%- if messages[0]['content'] is string -%}
188
- {{- messages[0]['content'] | trim -}}
189
- {%- elif messages[0]['content'] is sequence -%}
190
- {%- for item in messages[0]['content'] -%}
191
- {{- item['text'] | trim + ' '-}}
192
- {%- endfor -%}
193
- {%- endif -%}
194
- {%- set loop_messages = messages[1:] -%}
195
- {%- endif -%}
196
- {%- if tools -%}
197
- {%- for tool in tools %}
198
- {{- '<|tool>' -}}
199
- {{- format_function_declaration(tool) | trim -}}
200
- {{- '<tool|>' -}}
201
- {%- endfor %}
202
- {%- set ns.prev_message_type = 'tool' -%}
203
- {%- endif -%}
204
- {{- '<turn|>\n' -}}
205
- {%- endif %}
206
-
207
- {#- Pre-scan: find last user message index for reasoning guard -#}
208
- {%- set ns_turn = namespace(last_user_idx=-1) -%}
209
- {%- for i in range(loop_messages | length) -%}
210
- {%- if loop_messages[i]['role'] == 'user' -%}
211
- {%- set ns_turn.last_user_idx = i -%}
212
- {%- endif -%}
213
- {%- endfor -%}
214
-
215
- {#- Loop through messages -#}
216
- {%- for message in loop_messages -%}
217
- {%- if message['role'] != 'tool' -%}
218
- {%- set ns.prev_message_type = None -%}
219
- {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
220
- {#- Detect continuation: suppress duplicate <|turn>model when previous non-tool message was also assistant -#}
221
- {%- set prev_nt = namespace(role=None, found=false) -%}
222
- {%- if loop.index0 > 0 -%}
223
- {%- for j in range(loop.index0 - 1, -1, -1) -%}
224
- {%- if not prev_nt.found -%}
225
- {%- if loop_messages[j]['role'] != 'tool' -%}
226
- {%- set prev_nt.role = loop_messages[j]['role'] -%}
227
- {%- set prev_nt.found = true -%}
228
- {%- endif -%}
229
- {%- endif -%}
230
- {%- endfor -%}
231
- {%- endif -%}
232
- {%- set continue_same_model_turn = (role == 'model' and prev_nt.role == 'assistant') -%}
233
- {%- if not continue_same_model_turn -%}
234
- {{- '<|turn>' + role + '\n' }}
235
- {%- endif -%}
236
-
237
- {#- Render reasoning/reasoning_content as thinking channel -#}
238
- {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
239
- {%- if thinking_text and loop.index0 > ns_turn.last_user_idx and message.get('tool_calls') -%}
240
- {{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
241
- {%- endif -%}
242
-
243
- {%- if message['tool_calls'] -%}
244
- {%- for tool_call in message['tool_calls'] -%}
245
- {%- set function = tool_call['function'] -%}
246
- {{- '<|tool_call>call:' + function['name'] + '{' -}}
247
- {%- if function['arguments'] is mapping -%}
248
- {%- set ns_args = namespace(found_first=false) -%}
249
- {%- for key, value in function['arguments'] | dictsort -%}
250
- {%- if ns_args.found_first %},{% endif -%}
251
- {%- set ns_args.found_first = true -%}
252
- {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
253
- {%- endfor -%}
254
- {%- elif function['arguments'] is string -%}
255
- {{- function['arguments'] -}}
256
- {%- endif -%}
257
- {{- '}<tool_call|>' -}}
258
- {%- endfor -%}
259
- {%- set ns.prev_message_type = 'tool_call' -%}
260
- {%- endif -%}
261
-
262
- {%- set ns_tr_out = namespace(flag=false) -%}
263
- {%- if message.get('tool_responses') -%}
264
- {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
265
- {%- for tool_response in message['tool_responses'] -%}
266
- {{- format_tool_response_block(tool_response['name'] | default('unknown'), tool_response['response']) -}}
267
- {%- set ns_tr_out.flag = true -%}
268
- {%- set ns.prev_message_type = 'tool_response' -%}
269
- {%- endfor -%}
270
- {%- elif message.get('tool_calls') -%}
271
- {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
272
- {%- set ns_tool_scan = namespace(stopped=false) -%}
273
- {%- for k in range(loop.index0 + 1, loop_messages | length) -%}
274
- {%- if ns_tool_scan.stopped -%}
275
- {%- elif loop_messages[k]['role'] != 'tool' -%}
276
- {%- set ns_tool_scan.stopped = true -%}
277
- {%- else -%}
278
- {%- set follow = loop_messages[k] -%}
279
- {#- Resolve tool_call_id to function name -#}
280
- {%- set ns_tname = namespace(name=follow.get('name') | default('unknown')) -%}
281
- {%- for tc in message['tool_calls'] -%}
282
- {%- if tc.get('id') == follow.get('tool_call_id') -%}
283
- {%- set ns_tname.name = tc['function']['name'] -%}
284
- {%- endif -%}
285
- {%- endfor -%}
286
- {#- Handle content as string or content-parts array -#}
287
- {%- set tool_body = follow.get('content') -%}
288
- {%- if tool_body is string -%}
289
- {{- format_tool_response_block(ns_tname.name, tool_body) -}}
290
- {%- elif tool_body is sequence and tool_body is not string -%}
291
- {%- set ns_txt = namespace(s='') -%}
292
- {%- for part in tool_body -%}
293
- {%- if part.get('type') == 'text' -%}
294
- {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
295
- {%- endif -%}
296
- {%- endfor -%}
297
- {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
298
- {%- for part in tool_body -%}
299
- {%- if part.get('type') == 'image' -%}
300
- {{- '<|image|>' -}}
301
- {%- elif part.get('type') == 'audio' -%}
302
- {{- '<|audio|>' -}}
303
- {%- elif part.get('type') == 'video' -%}
304
- {{- '<|video|>' -}}
305
- {%- endif -%}
306
- {%- endfor -%}
307
- {%- else -%}
308
- {{- format_tool_response_block(ns_tname.name, tool_body) -}}
309
- {%- endif -%}
310
- {%- set ns_tr_out.flag = true -%}
311
- {%- set ns.prev_message_type = 'tool_response' -%}
312
- {%- endif -%}
313
- {%- endfor -%}
314
- {%- endif -%}
315
-
316
- {%- set captured_content -%}
317
- {%- if message['content'] is string -%}
318
- {%- if role == 'model' -%}
319
- {{- strip_thinking(message['content']) -}}
320
- {%- else -%}
321
- {{- message['content'] | trim -}}
322
- {%- endif -%}
323
- {%- elif message['content'] is sequence -%}
324
- {%- for item in message['content'] -%}
325
- {%- if item['type'] == 'text' -%}
326
- {%- if role == 'model' -%}
327
- {{- strip_thinking(item['text']) -}}
328
- {%- else -%}
329
- {{- item['text'] | trim -}}
330
- {%- endif -%}
331
- {%- elif item['type'] == 'image' -%}
332
- {{- '<|image|>' -}}
333
- {%- set ns.prev_message_type = 'image' -%}
334
- {%- elif item['type'] == 'audio' -%}
335
- {{- '<|audio|>' -}}
336
- {%- set ns.prev_message_type = 'audio' -%}
337
- {%- elif item['type'] == 'video' -%}
338
- {{- '<|video|>' -}}
339
- {%- set ns.prev_message_type = 'video' -%}
340
- {%- endif -%}
341
- {%- endfor -%}
342
- {%- endif -%}
343
- {%- endset -%}
344
-
345
- {{- captured_content -}}
346
- {%- set has_content = captured_content | trim | length > 0 -%}
347
-
348
- {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
349
- {{- '<|tool_response>' -}}
350
- {%- elif not (ns_tr_out.flag and not has_content) -%}
351
- {{- '<turn|>\n' -}}
352
- {%- endif -%}
353
- {%- endif -%}
354
- {%- endfor -%}
355
-
356
- {%- if add_generation_prompt -%}
357
- {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
358
- {{- '<|turn>model\n' -}}
359
- {%- endif -%}
360
  {%- endif -%}
 
1
+ {%- macro format_parameters(properties, required, filter_keys=false) -%}
2
+ {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
3
+ {%- set ns = namespace(found_first=false) -%}
4
+ {%- for key, value in properties | dictsort -%}
5
+ {%- set add_comma = false -%}
6
+ {%- if not filter_keys or key not in standard_keys -%}
7
+ {%- if ns.found_first %},{% endif -%}
8
+ {%- set ns.found_first = true -%}
9
+ {{ key }}:{
10
+ {%- if value['description'] -%}
11
+ description:<|"|>{{ value['description'] }}<|"|>
12
+ {%- set add_comma = true -%}
13
+ {%- endif -%}
14
+ {%- if value['type'] | upper == 'STRING' -%}
15
+ {%- if value['enum'] -%}
16
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
17
+ enum:{{ format_argument(value['enum']) }}
18
+ {%- endif -%}
19
+ {%- elif value['type'] | upper == 'ARRAY' -%}
20
+ {%- if value['items'] is mapping and value['items'] -%}
21
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
22
+ items:{
23
+ {%- set ns_items = namespace(found_first=false) -%}
24
+ {%- for item_key, item_value in value['items'] | dictsort -%}
25
+ {%- if item_value is not none -%}
26
+ {%- if ns_items.found_first %},{% endif -%}
27
+ {%- set ns_items.found_first = true -%}
28
+ {%- if item_key == 'properties' -%}
29
+ properties:{
30
+ {%- if item_value is mapping -%}
31
+ {{- format_parameters(item_value, value['items']['required'] | default([])) -}}
32
+ {%- endif -%}
33
+ }
34
+ {%- elif item_key == 'required' -%}
35
+ required:[
36
+ {%- for req_item in item_value -%}
37
+ <|"|>{{- req_item -}}<|"|>
38
+ {%- if not loop.last %},{% endif -%}
39
+ {%- endfor -%}
40
+ ]
41
+ {%- elif item_key == 'type' -%}
42
+ {%- if item_value is string -%}
43
+ type:{{ format_argument(item_value | upper) }}
44
+ {%- else -%}
45
+ type:{{ format_argument(item_value | map('upper') | list) }}
46
+ {%- endif -%}
47
+ {%- else -%}
48
+ {{ item_key }}:{{ format_argument(item_value) }}
49
+ {%- endif -%}
50
+ {%- endif -%}
51
+ {%- endfor -%}
52
+ }
53
+ {%- endif -%}
54
+ {%- endif -%}
55
+ {%- if value['nullable'] %}
56
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
57
+ nullable:true
58
+ {%- endif -%}
59
+ {%- if value['type'] | upper == 'OBJECT' -%}
60
+ {%- if value['properties'] is defined and value['properties'] is mapping -%}
61
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
62
+ properties:{
63
+ {{- format_parameters(value['properties'], value['required'] | default([])) -}}
64
+ }
65
+ {%- elif value is mapping -%}
66
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
67
+ properties:{
68
+ {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
69
+ }
70
+ {%- endif -%}
71
+ {%- if value['required'] -%}
72
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
73
+ required:[
74
+ {%- for item in value['required'] | default([]) -%}
75
+ <|"|>{{- item -}}<|"|>
76
+ {%- if not loop.last %},{% endif -%}
77
+ {%- endfor -%}
78
+ ]
79
+ {%- endif -%}
80
+ {%- endif -%}
81
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
82
+ type:<|"|>{{ value['type'] | upper }}<|"|>}
83
+ {%- endif -%}
84
+ {%- endfor -%}
85
+ {%- endmacro -%}
86
+ {%- macro format_function_declaration(tool_data) -%}
87
+ declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
88
+ {%- set params = tool_data['function']['parameters'] -%}
89
+ {%- if params -%}
90
+ ,parameters:{
91
+ {%- if params['properties'] -%}
92
+ properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
93
+ {%- endif -%}
94
+ {%- if params['required'] -%}
95
+ required:[
96
+ {%- for item in params['required'] -%}
97
+ <|"|>{{- item -}}<|"|>
98
+ {{- ',' if not loop.last -}}
99
+ {%- endfor -%}
100
+ ],
101
+ {%- endif -%}
102
+ {%- if params['type'] -%}
103
+ type:<|"|>{{- params['type'] | upper -}}<|"|>}
104
+ {%- endif -%}
105
+ {%- endif -%}
106
+ {%- if 'response' in tool_data['function'] -%}
107
+ {%- set response_declaration = tool_data['function']['response'] -%}
108
+ ,response:{
109
+ {%- if response_declaration['description'] -%}
110
+ description:<|"|>{{- response_declaration['description'] -}}<|"|>,
111
+ {%- endif -%}
112
+ {%- if response_declaration['type'] | upper == 'OBJECT' -%}
113
+ type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
114
+ {%- endif -%}
115
+ {%- endif -%}
116
+ }
117
+ {%- endmacro -%}
118
+ {%- macro format_argument(argument, escape_keys=True) -%}
119
+ {%- if argument is string -%}
120
+ {{- '<|"|>' + argument + '<|"|>' -}}
121
+ {%- elif argument is boolean -%}
122
+ {{- 'true' if argument else 'false' -}}
123
+ {%- elif argument is mapping -%}
124
+ {{- '{' -}}
125
+ {%- set ns = namespace(found_first=false) -%}
126
+ {%- for key, value in argument | dictsort -%}
127
+ {%- if ns.found_first %},{% endif -%}
128
+ {%- set ns.found_first = true -%}
129
+ {%- if escape_keys -%}
130
+ {{- '<|"|>' + key + '<|"|>' -}}
131
+ {%- else -%}
132
+ {{- key -}}
133
+ {%- endif -%}
134
+ :{{- format_argument(value, escape_keys=escape_keys) -}}
135
+ {%- endfor -%}
136
+ {{- '}' -}}
137
+ {%- elif argument is sequence -%}
138
+ {{- '[' -}}
139
+ {%- for item in argument -%}
140
+ {{- format_argument(item, escape_keys=escape_keys) -}}
141
+ {%- if not loop.last %},{% endif -%}
142
+ {%- endfor -%}
143
+ {{- ']' -}}
144
+ {%- else -%}
145
+ {{- argument -}}
146
+ {%- endif -%}
147
+ {%- endmacro -%}
148
+ {%- macro strip_thinking(text) -%}
149
+ {%- set ns = namespace(result='') -%}
150
+ {%- for part in text.split('<channel|>') -%}
151
+ {%- if '<|channel>' in part -%}
152
+ {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
153
+ {%- else -%}
154
+ {%- set ns.result = ns.result + part -%}
155
+ {%- endif -%}
156
+ {%- endfor -%}
157
+ {{- ns.result | trim -}}
158
+ {%- endmacro -%}
159
+
160
+ {%- macro format_tool_response_block(tool_name, response) -%}
161
+ {{- '<|tool_response>' -}}
162
+ {%- if response is mapping -%}
163
+ {{- 'response:' + tool_name + '{' -}}
164
+ {%- for key, value in response | dictsort -%}
165
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
166
+ {%- if not loop.last %},{% endif -%}
167
+ {%- endfor -%}
168
+ {{- '}' -}}
169
+ {%- else -%}
170
+ {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
171
+ {%- endif -%}
172
+ {{- '<tool_response|>' -}}
173
+ {%- endmacro -%}
174
+
175
+ {%- set ns = namespace(prev_message_type=None) -%}
176
+ {%- set loop_messages = messages -%}
177
+ {{- bos_token -}}
178
+ {#- Handle System/Tool Definitions Block -#}
179
+ {%- if (enable_thinking is defined and enable_thinking) or tools or messages[0]['role'] in ['system', 'developer'] -%}
180
+ {{- '<|turn>system\n' -}}
181
+ {#- Inject Thinking token at the very top of the FIRST system turn -#}
182
+ {%- if enable_thinking is defined and enable_thinking -%}
183
+ {{- '<|think|>\n' -}}
184
+ {%- set ns.prev_message_type = 'think' -%}
185
+ {%- endif -%}
186
+ {%- if messages[0]['role'] in ['system', 'developer'] -%}
187
+ {%- if messages[0]['content'] is string -%}
188
+ {{- messages[0]['content'] | trim -}}
189
+ {%- elif messages[0]['content'] is sequence -%}
190
+ {%- for item in messages[0]['content'] -%}
191
+ {{- item['text'] | trim + ' '-}}
192
+ {%- endfor -%}
193
+ {%- endif -%}
194
+ {%- set loop_messages = messages[1:] -%}
195
+ {%- endif -%}
196
+ {%- if tools -%}
197
+ {%- for tool in tools %}
198
+ {{- '<|tool>' -}}
199
+ {{- format_function_declaration(tool) | trim -}}
200
+ {{- '<tool|>' -}}
201
+ {%- endfor %}
202
+ {%- set ns.prev_message_type = 'tool' -%}
203
+ {%- endif -%}
204
+ {{- '<turn|>\n' -}}
205
+ {%- endif %}
206
+
207
+ {#- Pre-scan: find last user message index for reasoning guard -#}
208
+ {%- set ns_turn = namespace(last_user_idx=-1) -%}
209
+ {%- for i in range(loop_messages | length) -%}
210
+ {%- if loop_messages[i]['role'] == 'user' -%}
211
+ {%- set ns_turn.last_user_idx = i -%}
212
+ {%- endif -%}
213
+ {%- endfor -%}
214
+
215
+ {#- Loop through messages -#}
216
+ {%- for message in loop_messages -%}
217
+ {%- if message['role'] != 'tool' -%}
218
+ {%- set ns.prev_message_type = None -%}
219
+ {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
220
+ {#- Detect continuation: suppress duplicate <|turn>model when previous non-tool message was also assistant -#}
221
+ {%- set prev_nt = namespace(role=None, found=false) -%}
222
+ {%- if loop.index0 > 0 -%}
223
+ {%- for j in range(loop.index0 - 1, -1, -1) -%}
224
+ {%- if not prev_nt.found -%}
225
+ {%- if loop_messages[j]['role'] != 'tool' -%}
226
+ {%- set prev_nt.role = loop_messages[j]['role'] -%}
227
+ {%- set prev_nt.found = true -%}
228
+ {%- endif -%}
229
+ {%- endif -%}
230
+ {%- endfor -%}
231
+ {%- endif -%}
232
+ {%- set continue_same_model_turn = (role == 'model' and prev_nt.role == 'assistant') -%}
233
+ {%- if not continue_same_model_turn -%}
234
+ {{- '<|turn>' + role + '\n' }}
235
+ {%- endif -%}
236
+
237
+ {#- Render reasoning/reasoning_content as thinking channel -#}
238
+ {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
239
+ {%- if thinking_text and loop.index0 > ns_turn.last_user_idx and message.get('tool_calls') -%}
240
+ {{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
241
+ {%- endif -%}
242
+
243
+ {%- if message['tool_calls'] -%}
244
+ {%- for tool_call in message['tool_calls'] -%}
245
+ {%- set function = tool_call['function'] -%}
246
+ {{- '<|tool_call>call:' + function['name'] + '{' -}}
247
+ {%- if function['arguments'] is mapping -%}
248
+ {%- set ns_args = namespace(found_first=false) -%}
249
+ {%- for key, value in function['arguments'] | dictsort -%}
250
+ {%- if ns_args.found_first %},{% endif -%}
251
+ {%- set ns_args.found_first = true -%}
252
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
253
+ {%- endfor -%}
254
+ {%- elif function['arguments'] is string -%}
255
+ {{- function['arguments'] -}}
256
+ {%- endif -%}
257
+ {{- '}<tool_call|>' -}}
258
+ {%- endfor -%}
259
+ {%- set ns.prev_message_type = 'tool_call' -%}
260
+ {%- endif -%}
261
+
262
+ {%- set ns_tr_out = namespace(flag=false) -%}
263
+ {%- if message.get('tool_responses') -%}
264
+ {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
265
+ {%- for tool_response in message['tool_responses'] -%}
266
+ {{- format_tool_response_block(tool_response['name'] | default('unknown'), tool_response['response']) -}}
267
+ {%- set ns_tr_out.flag = true -%}
268
+ {%- set ns.prev_message_type = 'tool_response' -%}
269
+ {%- endfor -%}
270
+ {%- elif message.get('tool_calls') -%}
271
+ {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
272
+ {%- set ns_tool_scan = namespace(stopped=false) -%}
273
+ {%- for k in range(loop.index0 + 1, loop_messages | length) -%}
274
+ {%- if ns_tool_scan.stopped -%}
275
+ {%- elif loop_messages[k]['role'] != 'tool' -%}
276
+ {%- set ns_tool_scan.stopped = true -%}
277
+ {%- else -%}
278
+ {%- set follow = loop_messages[k] -%}
279
+ {#- Resolve tool_call_id to function name -#}
280
+ {%- set ns_tname = namespace(name=follow.get('name') | default('unknown')) -%}
281
+ {%- for tc in message['tool_calls'] -%}
282
+ {%- if tc.get('id') == follow.get('tool_call_id') -%}
283
+ {%- set ns_tname.name = tc['function']['name'] -%}
284
+ {%- endif -%}
285
+ {%- endfor -%}
286
+ {#- Handle content as string or content-parts array -#}
287
+ {%- set tool_body = follow.get('content') -%}
288
+ {%- if tool_body is string -%}
289
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
290
+ {%- elif tool_body is sequence and tool_body is not string -%}
291
+ {%- set ns_txt = namespace(s='') -%}
292
+ {%- for part in tool_body -%}
293
+ {%- if part.get('type') == 'text' -%}
294
+ {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
295
+ {%- endif -%}
296
+ {%- endfor -%}
297
+ {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
298
+ {%- for part in tool_body -%}
299
+ {%- if part.get('type') == 'image' -%}
300
+ {{- '<|image|>' -}}
301
+ {%- elif part.get('type') == 'audio' -%}
302
+ {{- '<|audio|>' -}}
303
+ {%- elif part.get('type') == 'video' -%}
304
+ {{- '<|video|>' -}}
305
+ {%- endif -%}
306
+ {%- endfor -%}
307
+ {%- else -%}
308
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
309
+ {%- endif -%}
310
+ {%- set ns_tr_out.flag = true -%}
311
+ {%- set ns.prev_message_type = 'tool_response' -%}
312
+ {%- endif -%}
313
+ {%- endfor -%}
314
+ {%- endif -%}
315
+
316
+ {%- set captured_content -%}
317
+ {%- if message['content'] is string -%}
318
+ {%- if role == 'model' -%}
319
+ {{- strip_thinking(message['content']) -}}
320
+ {%- else -%}
321
+ {{- message['content'] | trim -}}
322
+ {%- endif -%}
323
+ {%- elif message['content'] is sequence -%}
324
+ {%- for item in message['content'] -%}
325
+ {%- if item['type'] == 'text' -%}
326
+ {%- if role == 'model' -%}
327
+ {{- strip_thinking(item['text']) -}}
328
+ {%- else -%}
329
+ {{- item['text'] | trim -}}
330
+ {%- endif -%}
331
+ {%- elif item['type'] == 'image' -%}
332
+ {{- '<|image|>' -}}
333
+ {%- set ns.prev_message_type = 'image' -%}
334
+ {%- elif item['type'] == 'audio' -%}
335
+ {{- '<|audio|>' -}}
336
+ {%- set ns.prev_message_type = 'audio' -%}
337
+ {%- elif item['type'] == 'video' -%}
338
+ {{- '<|video|>' -}}
339
+ {%- set ns.prev_message_type = 'video' -%}
340
+ {%- endif -%}
341
+ {%- endfor -%}
342
+ {%- endif -%}
343
+ {%- endset -%}
344
+
345
+ {{- captured_content -}}
346
+ {%- set has_content = captured_content | trim | length > 0 -%}
347
+
348
+ {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
349
+ {{- '<|tool_response>' -}}
350
+ {%- elif not (ns_tr_out.flag and not has_content) -%}
351
+ {{- '<turn|>\n' -}}
352
+ {%- endif -%}
353
+ {%- endif -%}
354
+ {%- endfor -%}
355
+
356
+ {%- if add_generation_prompt -%}
357
+ {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
358
+ {{- '<|turn>model\n' -}}
359
+ {%- endif -%}
360
  {%- endif -%}
config.json CHANGED
@@ -1,198 +1,198 @@
1
- {
2
- "architectures": [
3
- "Gemma4ForConditionalGeneration"
4
- ],
5
- "audio_config": {
6
- "_name_or_path": "",
7
- "architectures": null,
8
- "attention_chunk_size": 12,
9
- "attention_context_left": 13,
10
- "attention_context_right": 0,
11
- "attention_invalid_logits_value": -1000000000.0,
12
- "attention_logit_cap": 50.0,
13
- "chunk_size_feed_forward": 0,
14
- "conv_kernel_size": 5,
15
- "dtype": "bfloat16",
16
- "gradient_clipping": 10000000000.0,
17
- "hidden_act": "silu",
18
- "hidden_size": 1024,
19
- "id2label": {
20
- "0": "LABEL_0",
21
- "1": "LABEL_1"
22
- },
23
- "initializer_range": 0.02,
24
- "is_encoder_decoder": false,
25
- "label2id": {
26
- "LABEL_0": 0,
27
- "LABEL_1": 1
28
- },
29
- "model_type": "gemma4_audio",
30
- "num_attention_heads": 8,
31
- "num_hidden_layers": 12,
32
- "output_attentions": false,
33
- "output_hidden_states": false,
34
- "output_proj_dims": 1536,
35
- "problem_type": null,
36
- "residual_weight": 0.5,
37
- "return_dict": true,
38
- "rms_norm_eps": 1e-06,
39
- "subsampling_conv_channels": [
40
- 128,
41
- 32
42
- ],
43
- "use_clipped_linears": true
44
- },
45
- "audio_token_id": 258881,
46
- "boa_token_id": 256000,
47
- "boi_token_id": 255999,
48
- "dtype": "bfloat16",
49
- "eoa_token_id": 258883,
50
- "eoa_token_index": 258883,
51
- "eoi_token_id": 258882,
52
- "eos_token_id": [
53
- 1,
54
- 106
55
- ],
56
- "image_token_id": 258880,
57
- "initializer_range": 0.02,
58
- "model_type": "gemma4",
59
- "text_config": {
60
- "attention_bias": false,
61
- "attention_dropout": 0.0,
62
- "attention_k_eq_v": false,
63
- "bos_token_id": 2,
64
- "dtype": "bfloat16",
65
- "enable_moe_block": false,
66
- "eos_token_id": 1,
67
- "expert_intermediate_size": null,
68
- "final_logit_softcapping": 30.0,
69
- "global_head_dim": 512,
70
- "head_dim": 256,
71
- "hidden_activation": "gelu_pytorch_tanh",
72
- "hidden_size": 2560,
73
- "hidden_size_per_layer_input": 256,
74
- "initializer_range": 0.02,
75
- "intermediate_size": 10240,
76
- "layer_types": [
77
- "sliding_attention",
78
- "sliding_attention",
79
- "sliding_attention",
80
- "sliding_attention",
81
- "sliding_attention",
82
- "full_attention",
83
- "sliding_attention",
84
- "sliding_attention",
85
- "sliding_attention",
86
- "sliding_attention",
87
- "sliding_attention",
88
- "full_attention",
89
- "sliding_attention",
90
- "sliding_attention",
91
- "sliding_attention",
92
- "sliding_attention",
93
- "sliding_attention",
94
- "full_attention",
95
- "sliding_attention",
96
- "sliding_attention",
97
- "sliding_attention",
98
- "sliding_attention",
99
- "sliding_attention",
100
- "full_attention",
101
- "sliding_attention",
102
- "sliding_attention",
103
- "sliding_attention",
104
- "sliding_attention",
105
- "sliding_attention",
106
- "full_attention",
107
- "sliding_attention",
108
- "sliding_attention",
109
- "sliding_attention",
110
- "sliding_attention",
111
- "sliding_attention",
112
- "full_attention",
113
- "sliding_attention",
114
- "sliding_attention",
115
- "sliding_attention",
116
- "sliding_attention",
117
- "sliding_attention",
118
- "full_attention"
119
- ],
120
- "max_position_embeddings": 131072,
121
- "model_type": "gemma4_text",
122
- "moe_intermediate_size": null,
123
- "num_attention_heads": 8,
124
- "num_experts": null,
125
- "num_global_key_value_heads": null,
126
- "num_hidden_layers": 42,
127
- "num_key_value_heads": 2,
128
- "num_kv_shared_layers": 18,
129
- "pad_token_id": 0,
130
- "rms_norm_eps": 1e-06,
131
- "rope_parameters": {
132
- "full_attention": {
133
- "partial_rotary_factor": 0.25,
134
- "rope_theta": 1000000.0,
135
- "rope_type": "proportional"
136
- },
137
- "sliding_attention": {
138
- "rope_theta": 10000.0,
139
- "rope_type": "default"
140
- }
141
- },
142
- "sliding_window": 512,
143
- "tie_word_embeddings": true,
144
- "top_k_experts": null,
145
- "use_bidirectional_attention": null,
146
- "use_cache": true,
147
- "use_double_wide_mlp": false,
148
- "vocab_size": 262144,
149
- "vocab_size_per_layer_input": 262144
150
- },
151
- "tie_word_embeddings": false,
152
- "transformers_version": "5.9.0",
153
- "video_token_id": 258884,
154
- "vision_config": {
155
- "_name_or_path": "",
156
- "architectures": null,
157
- "attention_bias": false,
158
- "attention_dropout": 0.0,
159
- "chunk_size_feed_forward": 0,
160
- "default_output_length": 280,
161
- "dtype": "bfloat16",
162
- "global_head_dim": 64,
163
- "head_dim": 64,
164
- "hidden_activation": "gelu_pytorch_tanh",
165
- "hidden_size": 768,
166
- "id2label": {
167
- "0": "LABEL_0",
168
- "1": "LABEL_1"
169
- },
170
- "initializer_range": 0.02,
171
- "intermediate_size": 3072,
172
- "is_encoder_decoder": false,
173
- "label2id": {
174
- "LABEL_0": 0,
175
- "LABEL_1": 1
176
- },
177
- "max_position_embeddings": 131072,
178
- "model_type": "gemma4_vision",
179
- "num_attention_heads": 12,
180
- "num_hidden_layers": 16,
181
- "num_key_value_heads": 12,
182
- "output_attentions": false,
183
- "output_hidden_states": false,
184
- "patch_size": 16,
185
- "pooling_kernel_size": 3,
186
- "position_embedding_size": 10240,
187
- "problem_type": null,
188
- "return_dict": true,
189
- "rms_norm_eps": 1e-06,
190
- "rope_parameters": {
191
- "rope_theta": 100.0,
192
- "rope_type": "default"
193
- },
194
- "standardize": false,
195
- "use_clipped_linears": true
196
- },
197
- "vision_soft_tokens_per_image": 280
198
- }
 
1
+ {
2
+ "architectures": [
3
+ "Gemma4ForConditionalGeneration"
4
+ ],
5
+ "audio_config": {
6
+ "_name_or_path": "",
7
+ "architectures": null,
8
+ "attention_chunk_size": 12,
9
+ "attention_context_left": 13,
10
+ "attention_context_right": 0,
11
+ "attention_invalid_logits_value": -1000000000.0,
12
+ "attention_logit_cap": 50.0,
13
+ "chunk_size_feed_forward": 0,
14
+ "conv_kernel_size": 5,
15
+ "dtype": "bfloat16",
16
+ "gradient_clipping": 10000000000.0,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 1024,
19
+ "id2label": {
20
+ "0": "LABEL_0",
21
+ "1": "LABEL_1"
22
+ },
23
+ "initializer_range": 0.02,
24
+ "is_encoder_decoder": false,
25
+ "label2id": {
26
+ "LABEL_0": 0,
27
+ "LABEL_1": 1
28
+ },
29
+ "model_type": "gemma4_audio",
30
+ "num_attention_heads": 8,
31
+ "num_hidden_layers": 12,
32
+ "output_attentions": false,
33
+ "output_hidden_states": false,
34
+ "output_proj_dims": 1536,
35
+ "problem_type": null,
36
+ "residual_weight": 0.5,
37
+ "return_dict": true,
38
+ "rms_norm_eps": 1e-06,
39
+ "subsampling_conv_channels": [
40
+ 128,
41
+ 32
42
+ ],
43
+ "use_clipped_linears": true
44
+ },
45
+ "audio_token_id": 258881,
46
+ "boa_token_id": 256000,
47
+ "boi_token_id": 255999,
48
+ "dtype": "bfloat16",
49
+ "eoa_token_id": 258883,
50
+ "eoa_token_index": 258883,
51
+ "eoi_token_id": 258882,
52
+ "eos_token_id": [
53
+ 1,
54
+ 106
55
+ ],
56
+ "image_token_id": 258880,
57
+ "initializer_range": 0.02,
58
+ "model_type": "gemma4",
59
+ "text_config": {
60
+ "attention_bias": false,
61
+ "attention_dropout": 0.0,
62
+ "attention_k_eq_v": false,
63
+ "bos_token_id": 2,
64
+ "dtype": "bfloat16",
65
+ "enable_moe_block": false,
66
+ "eos_token_id": 1,
67
+ "expert_intermediate_size": null,
68
+ "final_logit_softcapping": 30.0,
69
+ "global_head_dim": 512,
70
+ "head_dim": 256,
71
+ "hidden_activation": "gelu_pytorch_tanh",
72
+ "hidden_size": 2560,
73
+ "hidden_size_per_layer_input": 256,
74
+ "initializer_range": 0.02,
75
+ "intermediate_size": 10240,
76
+ "layer_types": [
77
+ "sliding_attention",
78
+ "sliding_attention",
79
+ "sliding_attention",
80
+ "sliding_attention",
81
+ "sliding_attention",
82
+ "full_attention",
83
+ "sliding_attention",
84
+ "sliding_attention",
85
+ "sliding_attention",
86
+ "sliding_attention",
87
+ "sliding_attention",
88
+ "full_attention",
89
+ "sliding_attention",
90
+ "sliding_attention",
91
+ "sliding_attention",
92
+ "sliding_attention",
93
+ "sliding_attention",
94
+ "full_attention",
95
+ "sliding_attention",
96
+ "sliding_attention",
97
+ "sliding_attention",
98
+ "sliding_attention",
99
+ "sliding_attention",
100
+ "full_attention",
101
+ "sliding_attention",
102
+ "sliding_attention",
103
+ "sliding_attention",
104
+ "sliding_attention",
105
+ "sliding_attention",
106
+ "full_attention",
107
+ "sliding_attention",
108
+ "sliding_attention",
109
+ "sliding_attention",
110
+ "sliding_attention",
111
+ "sliding_attention",
112
+ "full_attention",
113
+ "sliding_attention",
114
+ "sliding_attention",
115
+ "sliding_attention",
116
+ "sliding_attention",
117
+ "sliding_attention",
118
+ "full_attention"
119
+ ],
120
+ "max_position_embeddings": 131072,
121
+ "model_type": "gemma4_text",
122
+ "moe_intermediate_size": null,
123
+ "num_attention_heads": 8,
124
+ "num_experts": null,
125
+ "num_global_key_value_heads": null,
126
+ "num_hidden_layers": 42,
127
+ "num_key_value_heads": 2,
128
+ "num_kv_shared_layers": 18,
129
+ "pad_token_id": 0,
130
+ "rms_norm_eps": 1e-06,
131
+ "rope_parameters": {
132
+ "full_attention": {
133
+ "partial_rotary_factor": 0.25,
134
+ "rope_theta": 1000000.0,
135
+ "rope_type": "proportional"
136
+ },
137
+ "sliding_attention": {
138
+ "rope_theta": 10000.0,
139
+ "rope_type": "default"
140
+ }
141
+ },
142
+ "sliding_window": 512,
143
+ "tie_word_embeddings": true,
144
+ "top_k_experts": null,
145
+ "use_bidirectional_attention": null,
146
+ "use_cache": true,
147
+ "use_double_wide_mlp": false,
148
+ "vocab_size": 262144,
149
+ "vocab_size_per_layer_input": 262144
150
+ },
151
+ "tie_word_embeddings": false,
152
+ "transformers_version": "5.12.1",
153
+ "video_token_id": 258884,
154
+ "vision_config": {
155
+ "_name_or_path": "",
156
+ "architectures": null,
157
+ "attention_bias": false,
158
+ "attention_dropout": 0.0,
159
+ "chunk_size_feed_forward": 0,
160
+ "default_output_length": 280,
161
+ "dtype": "bfloat16",
162
+ "global_head_dim": 64,
163
+ "head_dim": 64,
164
+ "hidden_activation": "gelu_pytorch_tanh",
165
+ "hidden_size": 768,
166
+ "id2label": {
167
+ "0": "LABEL_0",
168
+ "1": "LABEL_1"
169
+ },
170
+ "initializer_range": 0.02,
171
+ "intermediate_size": 3072,
172
+ "is_encoder_decoder": false,
173
+ "label2id": {
174
+ "LABEL_0": 0,
175
+ "LABEL_1": 1
176
+ },
177
+ "max_position_embeddings": 131072,
178
+ "model_type": "gemma4_vision",
179
+ "num_attention_heads": 12,
180
+ "num_hidden_layers": 16,
181
+ "num_key_value_heads": 12,
182
+ "output_attentions": false,
183
+ "output_hidden_states": false,
184
+ "patch_size": 16,
185
+ "pooling_kernel_size": 3,
186
+ "position_embedding_size": 10240,
187
+ "problem_type": null,
188
+ "return_dict": true,
189
+ "rms_norm_eps": 1e-06,
190
+ "rope_parameters": {
191
+ "rope_theta": 100.0,
192
+ "rope_type": "default"
193
+ },
194
+ "standardize": false,
195
+ "use_clipped_linears": true
196
+ },
197
+ "vision_soft_tokens_per_image": 280
198
+ }
generation_config.json CHANGED
@@ -1,14 +1,14 @@
1
- {
2
- "bos_token_id": 2,
3
- "do_sample": true,
4
- "eos_token_id": [
5
- 1,
6
- 106,
7
- 50
8
- ],
9
- "pad_token_id": 0,
10
- "temperature": 1.0,
11
- "top_k": 64,
12
- "top_p": 0.95,
13
- "transformers_version": "5.9.0"
14
- }
 
1
+ {
2
+ "bos_token_id": 2,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 1,
6
+ 106,
7
+ 50
8
+ ],
9
+ "pad_token_id": 0,
10
+ "temperature": 1.0,
11
+ "top_k": 64,
12
+ "top_p": 0.95,
13
+ "transformers_version": "5.12.1"
14
+ }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:6f4163f95411e924dae0e41ff95358085e6aae4f5703541c7f82e132b8fd25f7
3
  size 17224656900
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:10c85c349bd7a4d6d14c698ed0b135adbcd905566302c31574a2cf38e10d6665
3
  size 17224656900
processor_config.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_ms_per_token": 40,
3
+ "audio_seq_length": 750,
4
+ "feature_extractor": {
5
+ "dither": 0.0,
6
+ "feature_extractor_type": "Gemma4AudioFeatureExtractor",
7
+ "feature_size": 128,
8
+ "fft_length": 512,
9
+ "fft_overdrive": false,
10
+ "frame_length": 320,
11
+ "hop_length": 160,
12
+ "input_scale_factor": 1.0,
13
+ "max_frequency": 8000.0,
14
+ "mel_floor": 0.001,
15
+ "min_frequency": 0.0,
16
+ "padding_side": "right",
17
+ "padding_value": 0.0,
18
+ "per_bin_mean": null,
19
+ "per_bin_stddev": null,
20
+ "preemphasis": 0.0,
21
+ "preemphasis_htk_flavor": true,
22
+ "return_attention_mask": true,
23
+ "sampling_rate": 16000
24
+ },
25
+ "image_processor": {
26
+ "do_convert_rgb": true,
27
+ "do_normalize": false,
28
+ "do_rescale": true,
29
+ "do_resize": true,
30
+ "image_mean": [
31
+ 0.0,
32
+ 0.0,
33
+ 0.0
34
+ ],
35
+ "image_processor_type": "Gemma4ImageProcessor",
36
+ "image_seq_length": 280,
37
+ "image_std": [
38
+ 1.0,
39
+ 1.0,
40
+ 1.0
41
+ ],
42
+ "max_soft_tokens": 280,
43
+ "patch_size": 16,
44
+ "pooling_kernel_size": 3,
45
+ "resample": 3,
46
+ "rescale_factor": 0.00392156862745098
47
+ },
48
+ "image_seq_length": 280,
49
+ "processor_class": "Gemma4Processor",
50
+ "video_processor": {
51
+ "do_convert_rgb": true,
52
+ "do_normalize": true,
53
+ "do_rescale": true,
54
+ "do_resize": true,
55
+ "do_sample_frames": true,
56
+ "image_mean": [
57
+ 0.0,
58
+ 0.0,
59
+ 0.0
60
+ ],
61
+ "image_std": [
62
+ 1.0,
63
+ 1.0,
64
+ 1.0
65
+ ],
66
+ "max_soft_tokens": 70,
67
+ "num_frames": 32,
68
+ "patch_size": 16,
69
+ "pooling_kernel_size": 3,
70
+ "resample": 3,
71
+ "rescale_factor": 0.00392156862745098,
72
+ "return_metadata": false,
73
+ "video_processor_type": "Gemma4VideoProcessor"
74
+ }
75
+ }
report.json CHANGED
@@ -1,146 +1,160 @@
1
- {
2
- "model": "google/gemma-4-E4B-it",
3
- "num_layers": 42,
4
- "hidden_size": 2560,
5
- "direction_layer": 25,
6
- "refusal_subspace_rank": 1,
7
- "initial_separation": 69.1039,
8
- "baseline_refusal_rate": 0.9583,
9
- "baseline_eval_n": 24,
10
- "edited_refusal_rate": 0.1524,
11
- "refusal_metric": "classifier + weak guard",
12
- "harmless_kl_nats": 0.2779,
13
- "kl_backoff_steps": 0,
14
- "kl_layer_trim_steps": 0,
15
- "repair_steps": 1,
16
- "guard_history": [],
17
- "layer_alphas": [
18
- 0.054,
19
- 0.034,
20
- 0.01,
21
- 0.0,
22
- 0.104,
23
- 0.013,
24
- 0.0,
25
- 0.019,
26
- 0.0,
27
- 0.047,
28
- 0.088,
29
- 0.361,
30
- 0.474,
31
- 0.625,
32
- 0.333,
33
- 0.0,
34
- 0.0,
35
- 0.0,
36
- 0.0,
37
- 0.0,
38
- 0.0,
39
- 0.0,
40
- 0.0,
41
- 0.0,
42
- 0.066,
43
- 2.463,
44
- 3.0,
45
- 1.985,
46
- 2.746,
47
- 2.307,
48
- 0.503,
49
- 2.371,
50
- 0.478,
51
- 0.835,
52
- 0.139,
53
- 0.95,
54
- 0.0,
55
- 0.0,
56
- 0.0,
57
- 0.163,
58
- 0.0,
59
- 0.0
60
- ],
61
- "ple_layer_alphas": [
62
- 0.0,
63
- 0.0,
64
- 0.0,
65
- 0.0,
66
- 0.0,
67
- 0.0,
68
- 0.0,
69
- 0.0,
70
- 0.0,
71
- 0.0,
72
- 0.0,
73
- 0.0,
74
- 0.0,
75
- 0.0,
76
- 0.0,
77
- 0.0,
78
- 0.0,
79
- 0.0,
80
- 0.0,
81
- 0.0,
82
- 0.0,
83
- 0.0,
84
- 0.0,
85
- 0.0,
86
- 0.0,
87
- 0.0,
88
- 0.0,
89
- 0.0,
90
- 0.0,
91
- 0.0,
92
- 0.0,
93
- 0.0,
94
- 0.0,
95
- 0.0,
96
- 0.0,
97
- 0.0,
98
- 0.0,
99
- 0.0,
100
- 0.0,
101
- 0.0,
102
- 0.0,
103
- 0.0
104
- ],
105
- "ple_embed_alpha": 0.0,
106
- "ple_model_projection_alpha": 0.0,
107
- "embed_alpha": 0.0,
108
- "head_alpha": 0.0,
109
- "head_token_alpha": 0.0,
110
- "preserve_rank": 8,
111
- "preserve_source": "harmless",
112
- "pruned_layers": [],
113
- "layers_after_prune": 42,
114
- "elapsed_sec": 1397.8,
115
- "profile": "balanced",
116
- "target_refusal": 0.03,
117
- "max_kl": 0.55,
118
- "kl_target": 0.3,
119
- "kl_positions": 32,
120
- "opt_capability": true,
121
- "opt_capability_weight": 1.0,
122
- "timings_sec": {
123
- "load_model": 29.7,
124
- "load_prompts": 5.8,
125
- "baseline_refusal": 17.1,
126
- "activation_fit": 49.2,
127
- "reader_profile": 186.7,
128
- "guard": 0.0,
129
- "refine_refusal": 0.0,
130
- "validation_metrics": 107.8,
131
- "repair": 595.2,
132
- "prune": 0.0,
133
- "test_metrics": 162.7,
134
- "bake": 243.7
135
- },
136
- "command": "apostate ablate --model google/gemma-4-E4B-it --out gemma-4-e4b-it-apostate",
137
- "optimized": true,
138
- "reader_mode": true,
139
- "reader_strength": 3.0,
140
- "best_trial": {
141
- "refusal": 0.21875,
142
- "kl": 0.2761850953102112
143
- },
144
- "n_trials": 0,
145
- "baked_to": "gemma-4-e4b-it-apostate"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
 
1
+ {
2
+ "model": "google/gemma-4-e4b-it",
3
+ "num_layers": 42,
4
+ "hidden_size": 2560,
5
+ "direction_layer": 25,
6
+ "refusal_subspace_rank": 1,
7
+ "max_refusal_rank": 3,
8
+ "multi_refusal": true,
9
+ "multi_refusal_clusters": 6,
10
+ "multi_refusal_min_norm": 0.08,
11
+ "multi_refusal_min_separation": 0.05,
12
+ "multi_refusal_min_coverage": 0.05,
13
+ "initial_separation": 68.4185,
14
+ "baseline_refusal_rate": 0.9583,
15
+ "baseline_eval_n": 24,
16
+ "edited_refusal_rate": 0.019,
17
+ "refusal_metric": "classifier + weak guard",
18
+ "harmless_kl_nats": 0.145,
19
+ "kl_backoff_steps": 0,
20
+ "kl_layer_trim_steps": 0,
21
+ "repair_steps": 4,
22
+ "residual_repair": [],
23
+ "guard_history": [],
24
+ "layer_alphas": [
25
+ 1.695,
26
+ 0.0,
27
+ 0.029,
28
+ 0.003,
29
+ 0.055,
30
+ 0.046,
31
+ 0.0,
32
+ 0.0,
33
+ 0.049,
34
+ 0.0,
35
+ 0.0,
36
+ 0.0,
37
+ 0.4,
38
+ 0.159,
39
+ 0.0,
40
+ 0.0,
41
+ 0.165,
42
+ 0.0,
43
+ 0.025,
44
+ 0.0,
45
+ 0.279,
46
+ 0.0,
47
+ 0.0,
48
+ 0.0,
49
+ 0.474,
50
+ 3.871,
51
+ 3.2,
52
+ 2.455,
53
+ 6.715,
54
+ 2.297,
55
+ 0.746,
56
+ 4.033,
57
+ 0.328,
58
+ 0.64,
59
+ 0.713,
60
+ 0.591,
61
+ 0.0,
62
+ 0.0,
63
+ 0.0,
64
+ 0.003,
65
+ 0.0,
66
+ 0.0
67
+ ],
68
+ "ple_layer_alphas": [
69
+ 0.0,
70
+ 0.0,
71
+ 0.0,
72
+ 0.0,
73
+ 0.0,
74
+ 0.0,
75
+ 0.0,
76
+ 0.0,
77
+ 0.0,
78
+ 0.0,
79
+ 0.0,
80
+ 0.0,
81
+ 0.0,
82
+ 0.0,
83
+ 0.0,
84
+ 0.0,
85
+ 0.0,
86
+ 0.0,
87
+ 0.0,
88
+ 0.0,
89
+ 0.0,
90
+ 0.0,
91
+ 0.0,
92
+ 0.0,
93
+ 0.0,
94
+ 0.0,
95
+ 0.0,
96
+ 0.0,
97
+ 0.0,
98
+ 0.0,
99
+ 0.0,
100
+ 0.0,
101
+ 0.0,
102
+ 0.0,
103
+ 0.0,
104
+ 0.0,
105
+ 0.0,
106
+ 0.0,
107
+ 0.0,
108
+ 0.0,
109
+ 0.0,
110
+ 0.0
111
+ ],
112
+ "ple_embed_alpha": 0.0,
113
+ "ple_model_projection_alpha": 0.0,
114
+ "embed_alpha": 0.0,
115
+ "head_alpha": 0.0,
116
+ "head_token_alpha": 0.0,
117
+ "preserve_rank": 8,
118
+ "preserve_source": "harmless",
119
+ "pruned_layers": [],
120
+ "layers_after_prune": 42,
121
+ "elapsed_sec": 1027.6,
122
+ "profile": "balanced",
123
+ "target_refusal": 0.05,
124
+ "max_kl": 0.15,
125
+ "kl_target": 0.3,
126
+ "refusal_eval_path": "JailbreakBench/JBB-Behaviors@behaviors:harmful:Goal|/var/home/Heterodoxin/apostate/data/refusal_calibration.txt",
127
+ "refusal_eval_n": 64,
128
+ "kl_positions": 8,
129
+ "kl_eval_path": "mlabonne/harmless_alpaca:test:text",
130
+ "kl_eval_n": 48,
131
+ "oblique_ablation": false,
132
+ "oblique_strength": 1.0,
133
+ "opt_capability": true,
134
+ "opt_capability_weight": 2.5,
135
+ "timings_sec": {
136
+ "load_model": 10.6,
137
+ "load_prompts": 5.2,
138
+ "baseline_refusal": 42.9,
139
+ "activation_fit": 211.6,
140
+ "reader_profile": 57.1,
141
+ "reader_guard": 20.1,
142
+ "guard": 0.0,
143
+ "refine_refusal": 0.0,
144
+ "validation_metrics": 91.3,
145
+ "repair": 235.6,
146
+ "prune": 0.0,
147
+ "test_metrics": 336.7,
148
+ "bake": 16.6
149
+ },
150
+ "command": "/var/home/Heterodoxin/apostate/apostate/cli.py --optimize --n-trials 8 --model google/gemma-4-e4b-it --output-dir /var/home/Heterodoxin/gemma4_e4b_lowkl_out --oblique-predictive --target-refusal 0.05 --max-kl 0.15 --reader-max-kl 0.15",
151
+ "optimized": true,
152
+ "reader_mode": true,
153
+ "reader_strength": 2.0,
154
+ "best_trial": {
155
+ "refusal": 0.375,
156
+ "kl": 1.676835060119629
157
+ },
158
+ "n_trials": 0,
159
+ "baked_to": "/var/home/Heterodoxin/gemma4_e4b_lowkl_out"
160
  }
report.md CHANGED
@@ -1,106 +1,112 @@
1
- # Apostate Run Report
2
-
3
- ## Summary
4
- | Metric | Value |
5
- | --- | --- |
6
- | Base model | google/gemma-4-E4B-it |
7
- | Profile | balanced |
8
- | Output | gemma-4-e4b-it-apostate |
9
- | Layers | 42 |
10
- | Hidden size | 2560 |
11
- | Direction layer | 25 |
12
- | Baseline refusal (n=24) | 95.8% |
13
- | Edited refusal | 15.2% |
14
- | Refusal metric | classifier + weak guard |
15
- | Harmless KL | 0.278 |
16
- | Target refusal | 3.0% |
17
- | KL target | 0.300 |
18
- | KL budget | 0.550 |
19
- | KL positions | 32 |
20
- | KL layer trims | 0 |
21
- | Repair steps | 1 |
22
- | Preserve rank | 8 |
23
- | Preserve source | harmless |
24
- | Capability penalty | True |
25
- | Elapsed | 1397.8 sec |
26
-
27
- ## Command
28
-
29
- ```text
30
- apostate ablate --model google/gemma-4-E4B-it --out gemma-4-e4b-it-apostate
31
- ```
32
-
33
- ## Best Trial
34
- | Metric | Value |
35
- | --- | --- |
36
- | refusal | 0.21875 |
37
- | kl | 0.2761850953102112 |
38
-
39
- ## Layer Alphas
40
- | Layer | Alpha |
41
- | --- | --- |
42
- | 0 | 0.054 |
43
- | 1 | 0.034 |
44
- | 2 | 0.010 |
45
- | 3 | 0.000 |
46
- | 4 | 0.104 |
47
- | 5 | 0.013 |
48
- | 6 | 0.000 |
49
- | 7 | 0.019 |
50
- | 8 | 0.000 |
51
- | 9 | 0.047 |
52
- | 10 | 0.088 |
53
- | 11 | 0.361 |
54
- | 12 | 0.474 |
55
- | 13 | 0.625 |
56
- | 14 | 0.333 |
57
- | 15 | 0.000 |
58
- | 16 | 0.000 |
59
- | 17 | 0.000 |
60
- | 18 | 0.000 |
61
- | 19 | 0.000 |
62
- | 20 | 0.000 |
63
- | 21 | 0.000 |
64
- | 22 | 0.000 |
65
- | 23 | 0.000 |
66
- | 24 | 0.066 |
67
- | 25 | 2.463 |
68
- | 26 | 3.000 |
69
- | 27 | 1.985 |
70
- | 28 | 2.746 |
71
- | 29 | 2.307 |
72
- | 30 | 0.503 |
73
- | 31 | 2.371 |
74
- | 32 | 0.478 |
75
- | 33 | 0.835 |
76
- | 34 | 0.139 |
77
- | 35 | 0.950 |
78
- | 36 | 0.000 |
79
- | 37 | 0.000 |
80
- | 38 | 0.000 |
81
- | 39 | 0.163 |
82
- | 40 | 0.000 |
83
- | 41 | 0.000 |
84
-
85
- ## Timings
86
- | Phase | Seconds |
87
- | --- | --- |
88
- | load_model | 29.7 |
89
- | load_prompts | 5.8 |
90
- | baseline_refusal | 17.1 |
91
- | activation_fit | 49.2 |
92
- | reader_profile | 186.7 |
93
- | guard | 0.0 |
94
- | refine_refusal | 0.0 |
95
- | validation_metrics | 107.8 |
96
- | repair | 595.2 |
97
- | prune | 0.0 |
98
- | test_metrics | 162.7 |
99
- | bake | 243.7 |
100
-
101
- ## Measurement
102
- | field | value |
103
- | --- | --- |
104
- | refusal judge | classifier + weak guard |
105
- | preservation metric | harmless kl |
106
- | capability suites | gsm8k, humaneval, mbpp |
 
 
 
 
 
 
 
1
+ # Apostate Run Report
2
+
3
+ ## Summary
4
+ | Metric | Value |
5
+ | --- | --- |
6
+ | Base model | google/gemma-4-e4b-it |
7
+ | Profile | balanced |
8
+ | Output | /var/home/Heterodoxin/gemma4_e4b_lowkl_out |
9
+ | Layers | 42 |
10
+ | Hidden size | 2560 |
11
+ | Direction layer | 25 |
12
+ | Refusal rank | 1 |
13
+ | Max refusal rank | 3 |
14
+ | Multi refusal | True |
15
+ | Multi clusters | 6 |
16
+ | Multi min coverage | 0.05 |
17
+ | Baseline refusal (n=24) | 95.8% |
18
+ | Edited refusal | 1.9% |
19
+ | Refusal metric | classifier + weak guard |
20
+ | Harmless KL | 0.145 |
21
+ | Target refusal | 5.0% |
22
+ | KL target | 0.300 |
23
+ | KL budget | 0.150 |
24
+ | KL positions | 8 |
25
+ | KL layer trims | 0 |
26
+ | Repair steps | 4 |
27
+ | Preserve rank | 8 |
28
+ | Preserve source | harmless |
29
+ | Capability penalty | True |
30
+ | Elapsed | 1027.6 sec |
31
+
32
+ ## Command
33
+
34
+ ```text
35
+ /var/home/Heterodoxin/apostate/apostate/cli.py --optimize --n-trials 8 --model google/gemma-4-e4b-it --output-dir /var/home/Heterodoxin/gemma4_e4b_lowkl_out --oblique-predictive --target-refusal 0.05 --max-kl 0.15 --reader-max-kl 0.15
36
+ ```
37
+
38
+ ## Best Trial
39
+ | Metric | Value |
40
+ | --- | --- |
41
+ | refusal | 0.375 |
42
+ | kl | 1.676835060119629 |
43
+
44
+ ## Layer Alphas
45
+ | Layer | Alpha |
46
+ | --- | --- |
47
+ | 0 | 1.695 |
48
+ | 1 | 0.000 |
49
+ | 2 | 0.029 |
50
+ | 3 | 0.003 |
51
+ | 4 | 0.055 |
52
+ | 5 | 0.046 |
53
+ | 6 | 0.000 |
54
+ | 7 | 0.000 |
55
+ | 8 | 0.049 |
56
+ | 9 | 0.000 |
57
+ | 10 | 0.000 |
58
+ | 11 | 0.000 |
59
+ | 12 | 0.400 |
60
+ | 13 | 0.159 |
61
+ | 14 | 0.000 |
62
+ | 15 | 0.000 |
63
+ | 16 | 0.165 |
64
+ | 17 | 0.000 |
65
+ | 18 | 0.025 |
66
+ | 19 | 0.000 |
67
+ | 20 | 0.279 |
68
+ | 21 | 0.000 |
69
+ | 22 | 0.000 |
70
+ | 23 | 0.000 |
71
+ | 24 | 0.474 |
72
+ | 25 | 3.871 |
73
+ | 26 | 3.200 |
74
+ | 27 | 2.455 |
75
+ | 28 | 6.715 |
76
+ | 29 | 2.297 |
77
+ | 30 | 0.746 |
78
+ | 31 | 4.033 |
79
+ | 32 | 0.328 |
80
+ | 33 | 0.640 |
81
+ | 34 | 0.713 |
82
+ | 35 | 0.591 |
83
+ | 36 | 0.000 |
84
+ | 37 | 0.000 |
85
+ | 38 | 0.000 |
86
+ | 39 | 0.003 |
87
+ | 40 | 0.000 |
88
+ | 41 | 0.000 |
89
+
90
+ ## Timings
91
+ | Phase | Seconds |
92
+ | --- | --- |
93
+ | load_model | 10.6 |
94
+ | load_prompts | 5.2 |
95
+ | baseline_refusal | 42.9 |
96
+ | activation_fit | 211.6 |
97
+ | reader_profile | 57.1 |
98
+ | reader_guard | 20.1 |
99
+ | guard | 0.0 |
100
+ | refine_refusal | 0.0 |
101
+ | validation_metrics | 91.3 |
102
+ | repair | 235.6 |
103
+ | prune | 0.0 |
104
+ | test_metrics | 336.7 |
105
+ | bake | 16.6 |
106
+
107
+ ## Measurement
108
+ | field | value |
109
+ | --- | --- |
110
+ | refusal judge | classifier + weak guard |
111
+ | preservation metric | harmless kl |
112
+ | capability suites | gsm8k, humaneval, mbpp |
tokenizer_config.json CHANGED
@@ -1,96 +1,96 @@
1
- {
2
- "audio_token": "<|audio|>",
3
- "backend": "tokenizers",
4
- "boa_token": "<|audio>",
5
- "boi_token": "<|image>",
6
- "bos_token": "<bos>",
7
- "eoa_token": "<audio|>",
8
- "eoc_token": "<channel|>",
9
- "eoi_token": "<image|>",
10
- "eos_token": "<eos>",
11
- "eot_token": "<turn|>",
12
- "escape_token": "<|\"|>",
13
- "etc_token": "<tool_call|>",
14
- "etd_token": "<tool|>",
15
- "etr_token": "<tool_response|>",
16
- "extra_special_tokens": [
17
- "<|video|>"
18
- ],
19
- "image_token": "<|image|>",
20
- "is_local": false,
21
- "local_files_only": false,
22
- "mask_token": "<mask>",
23
- "model_max_length": 1000000000000000019884624838656,
24
- "model_specific_special_tokens": {
25
- "audio_token": "<|audio|>",
26
- "boa_token": "<|audio>",
27
- "boi_token": "<|image>",
28
- "eoa_token": "<audio|>",
29
- "eoc_token": "<channel|>",
30
- "eoi_token": "<image|>",
31
- "eot_token": "<turn|>",
32
- "escape_token": "<|\"|>",
33
- "etc_token": "<tool_call|>",
34
- "etd_token": "<tool|>",
35
- "etr_token": "<tool_response|>",
36
- "image_token": "<|image|>",
37
- "soc_token": "<|channel>",
38
- "sot_token": "<|turn>",
39
- "stc_token": "<|tool_call>",
40
- "std_token": "<|tool>",
41
- "str_token": "<|tool_response>",
42
- "think_token": "<|think|>"
43
- },
44
- "pad_token": "<pad>",
45
- "padding_side": "left",
46
- "processor_class": "Gemma4Processor",
47
- "response_schema": {
48
- "properties": {
49
- "content": {
50
- "type": "string"
51
- },
52
- "role": {
53
- "const": "assistant"
54
- },
55
- "thinking": {
56
- "type": "string"
57
- },
58
- "tool_calls": {
59
- "items": {
60
- "properties": {
61
- "function": {
62
- "properties": {
63
- "arguments": {
64
- "additionalProperties": {},
65
- "type": "object",
66
- "x-parser": "gemma4-tool-call"
67
- },
68
- "name": {
69
- "type": "string"
70
- }
71
- },
72
- "type": "object",
73
- "x-regex": "call\\:(?P<name>\\w+)(?P<arguments>\\{.*\\})"
74
- },
75
- "type": {
76
- "const": "function"
77
- }
78
- },
79
- "type": "object"
80
- },
81
- "type": "array",
82
- "x-regex-iterator": "<\\|tool_call>(.*?)<tool_call\\|>"
83
- }
84
- },
85
- "type": "object",
86
- "x-regex": "(\\<\\|channel\\>thought\\n(?P<thinking>.*?)\\<channel\\|\\>)?(?P<tool_calls>\\<\\|tool_call\\>.*\\<tool_call\\|\\>)?(?P<content>(?:(?!\\<turn\\|\\>)(?!\\<\\|tool_response\\>).)+)?(?:\\<turn\\|\\>|\\<\\|tool_response\\>)?"
87
- },
88
- "soc_token": "<|channel>",
89
- "sot_token": "<|turn>",
90
- "stc_token": "<|tool_call>",
91
- "std_token": "<|tool>",
92
- "str_token": "<|tool_response>",
93
- "think_token": "<|think|>",
94
- "tokenizer_class": "GemmaTokenizer",
95
- "unk_token": "<unk>"
96
- }
 
1
+ {
2
+ "audio_token": "<|audio|>",
3
+ "backend": "tokenizers",
4
+ "boa_token": "<|audio>",
5
+ "boi_token": "<|image>",
6
+ "bos_token": "<bos>",
7
+ "eoa_token": "<audio|>",
8
+ "eoc_token": "<channel|>",
9
+ "eoi_token": "<image|>",
10
+ "eos_token": "<eos>",
11
+ "eot_token": "<turn|>",
12
+ "escape_token": "<|\"|>",
13
+ "etc_token": "<tool_call|>",
14
+ "etd_token": "<tool|>",
15
+ "etr_token": "<tool_response|>",
16
+ "extra_special_tokens": [
17
+ "<|video|>"
18
+ ],
19
+ "image_token": "<|image|>",
20
+ "is_local": false,
21
+ "local_files_only": false,
22
+ "mask_token": "<mask>",
23
+ "model_max_length": 1000000000000000019884624838656,
24
+ "model_specific_special_tokens": {
25
+ "audio_token": "<|audio|>",
26
+ "boa_token": "<|audio>",
27
+ "boi_token": "<|image>",
28
+ "eoa_token": "<audio|>",
29
+ "eoc_token": "<channel|>",
30
+ "eoi_token": "<image|>",
31
+ "eot_token": "<turn|>",
32
+ "escape_token": "<|\"|>",
33
+ "etc_token": "<tool_call|>",
34
+ "etd_token": "<tool|>",
35
+ "etr_token": "<tool_response|>",
36
+ "image_token": "<|image|>",
37
+ "soc_token": "<|channel>",
38
+ "sot_token": "<|turn>",
39
+ "stc_token": "<|tool_call>",
40
+ "std_token": "<|tool>",
41
+ "str_token": "<|tool_response>",
42
+ "think_token": "<|think|>"
43
+ },
44
+ "pad_token": "<pad>",
45
+ "padding_side": "left",
46
+ "processor_class": "Gemma4Processor",
47
+ "response_schema": {
48
+ "properties": {
49
+ "content": {
50
+ "type": "string"
51
+ },
52
+ "role": {
53
+ "const": "assistant"
54
+ },
55
+ "thinking": {
56
+ "type": "string"
57
+ },
58
+ "tool_calls": {
59
+ "items": {
60
+ "properties": {
61
+ "function": {
62
+ "properties": {
63
+ "arguments": {
64
+ "additionalProperties": {},
65
+ "type": "object",
66
+ "x-parser": "gemma4-tool-call"
67
+ },
68
+ "name": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "type": "object",
73
+ "x-regex": "call\\:(?P<name>\\w+)(?P<arguments>\\{.*\\})"
74
+ },
75
+ "type": {
76
+ "const": "function"
77
+ }
78
+ },
79
+ "type": "object"
80
+ },
81
+ "type": "array",
82
+ "x-regex-iterator": "<\\|tool_call>(.*?)<tool_call\\|>"
83
+ }
84
+ },
85
+ "type": "object",
86
+ "x-regex": "(\\<\\|channel\\>thought\\n(?P<thinking>.*?)\\<channel\\|\\>)?(?P<tool_calls>\\<\\|tool_call\\>.*\\<tool_call\\|\\>)?(?P<content>(?:(?!\\<turn\\|\\>)(?!\\<\\|tool_response\\>).)+)?(?:\\<turn\\|\\>|\\<\\|tool_response\\>)?"
87
+ },
88
+ "soc_token": "<|channel>",
89
+ "sot_token": "<|turn>",
90
+ "stc_token": "<|tool_call>",
91
+ "std_token": "<|tool>",
92
+ "str_token": "<|tool_response>",
93
+ "think_token": "<|think|>",
94
+ "tokenizer_class": "GemmaTokenizer",
95
+ "unk_token": "<unk>"
96
+ }