TuHan commited on
Commit
3f2601a
·
verified ·
1 Parent(s): bc90807

Duplicate of the tiny-nla weights (same training run)

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ code-decoder/tokenizer.json filter=lfs diff=lfs merge=lfs -text
37
+ nla/actor/tokenizer.json filter=lfs diff=lfs merge=lfs -text
38
+ nla/critic/tokenizer.json filter=lfs diff=lfs merge=lfs -text
code-decoder/centre_mean.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ea65c6f3af1d03f1f0084b9e6eac98bd8263b28dc843f38c04a4e4274544c1e
3
+ size 16512
code-decoder/chat_template.jinja ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if tools %}
2
+ {{- '<|im_start|>system\n' }}
3
+ {%- if messages[0].role == 'system' %}
4
+ {{- messages[0].content + '\n\n' }}
5
+ {%- endif %}
6
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
7
+ {%- for tool in tools %}
8
+ {{- "\n" }}
9
+ {{- tool | tojson }}
10
+ {%- endfor %}
11
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
12
+ {%- else %}
13
+ {%- if messages[0].role == 'system' %}
14
+ {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
15
+ {%- endif %}
16
+ {%- endif %}
17
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
18
+ {%- for message in messages[::-1] %}
19
+ {%- set index = (messages|length - 1) - loop.index0 %}
20
+ {%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
21
+ {%- set ns.multi_step_tool = false %}
22
+ {%- set ns.last_query_index = index %}
23
+ {%- endif %}
24
+ {%- endfor %}
25
+ {%- for message in messages %}
26
+ {%- if message.content is string %}
27
+ {%- set content = message.content %}
28
+ {%- else %}
29
+ {%- set content = '' %}
30
+ {%- endif %}
31
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
32
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
33
+ {%- elif message.role == "assistant" %}
34
+ {%- set reasoning_content = '' %}
35
+ {%- if message.reasoning_content is string %}
36
+ {%- set reasoning_content = message.reasoning_content %}
37
+ {%- else %}
38
+ {%- if '</think>' in content %}
39
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
40
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
41
+ {%- endif %}
42
+ {%- endif %}
43
+ {%- if loop.index0 > ns.last_query_index %}
44
+ {%- if loop.last or (not loop.last and reasoning_content) %}
45
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
46
+ {%- else %}
47
+ {{- '<|im_start|>' + message.role + '\n' + content }}
48
+ {%- endif %}
49
+ {%- else %}
50
+ {{- '<|im_start|>' + message.role + '\n' + content }}
51
+ {%- endif %}
52
+ {%- if message.tool_calls %}
53
+ {%- for tool_call in message.tool_calls %}
54
+ {%- if (loop.first and content) or (not loop.first) %}
55
+ {{- '\n' }}
56
+ {%- endif %}
57
+ {%- if tool_call.function %}
58
+ {%- set tool_call = tool_call.function %}
59
+ {%- endif %}
60
+ {{- '<tool_call>\n{"name": "' }}
61
+ {{- tool_call.name }}
62
+ {{- '", "arguments": ' }}
63
+ {%- if tool_call.arguments is string %}
64
+ {{- tool_call.arguments }}
65
+ {%- else %}
66
+ {{- tool_call.arguments | tojson }}
67
+ {%- endif %}
68
+ {{- '}\n</tool_call>' }}
69
+ {%- endfor %}
70
+ {%- endif %}
71
+ {{- '<|im_end|>\n' }}
72
+ {%- elif message.role == "tool" %}
73
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
74
+ {{- '<|im_start|>user' }}
75
+ {%- endif %}
76
+ {{- '\n<tool_response>\n' }}
77
+ {{- content }}
78
+ {{- '\n</tool_response>' }}
79
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
80
+ {{- '<|im_end|>\n' }}
81
+ {%- endif %}
82
+ {%- endif %}
83
+ {%- endfor %}
84
+ {%- if add_generation_prompt %}
85
+ {{- '<|im_start|>assistant\n' }}
86
+ {%- if enable_thinking is defined and enable_thinking is false %}
87
+ {{- '<think>\n\n</think>\n\n' }}
88
+ {%- endif %}
89
+ {%- endif %}
code-decoder/config.json ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 151643,
8
+ "dtype": "float32",
9
+ "eos_token_id": 151645,
10
+ "head_dim": 128,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 4096,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 12288,
15
+ "layer_types": [
16
+ "full_attention",
17
+ "full_attention",
18
+ "full_attention",
19
+ "full_attention",
20
+ "full_attention",
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention",
44
+ "full_attention",
45
+ "full_attention",
46
+ "full_attention",
47
+ "full_attention",
48
+ "full_attention",
49
+ "full_attention",
50
+ "full_attention",
51
+ "full_attention"
52
+ ],
53
+ "max_position_embeddings": 40960,
54
+ "max_window_layers": 36,
55
+ "model_type": "qwen3",
56
+ "num_attention_heads": 32,
57
+ "num_hidden_layers": 36,
58
+ "num_key_value_heads": 8,
59
+ "pad_token_id": null,
60
+ "rms_norm_eps": 1e-06,
61
+ "rope_parameters": {
62
+ "rope_theta": 1000000,
63
+ "rope_type": "default"
64
+ },
65
+ "sliding_window": null,
66
+ "tie_word_embeddings": false,
67
+ "transformers_version": "5.3.0",
68
+ "use_cache": true,
69
+ "use_sliding_window": false,
70
+ "vocab_size": 151936
71
+ }
code-decoder/generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 151645,
6
+ 151643
7
+ ],
8
+ "pad_token_id": 151643,
9
+ "temperature": 0.6,
10
+ "top_k": 20,
11
+ "top_p": 0.95,
12
+ "transformers_version": "5.3.0"
13
+ }
code-decoder/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:00e991281cbfd766354a83b798f744b61f2518ca4a7ea5d6ad67d7b97660fcf5
3
+ size 16381517208
code-decoder/nla_meta.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ kind: nla_dataset
2
+ schema_version: 2
3
+ dataset_id: av_sft_train.parquet
4
+ split_type: av_sft
5
+ row_count: 124741
6
+ extraction:
7
+ d_model: 4096
8
+ injection_scale: null
9
+ mse_scale: sqrt_d_model
10
+ norm: none
11
+ tokens:
12
+ injection_char: ㈎
13
+ injection_token_id: 149705
14
+ injection_left_neighbor_id: 29
15
+ injection_right_neighbor_id: 522
16
+ prompt_templates:
17
+ actor: 'You are a meticulous AI researcher conducting an important investigation
18
+ into activation vectors from a language model. Your overall task is to describe
19
+ the semantic content of that activation vector.
20
+
21
+
22
+ We will pass the vector enclosed in <concept> tags into your context. You must
23
+ then produce an explanation for the vector, enclosed within <explanation> tags.
24
+ The explanation consists of 2-3 text snippets describing that vector.
25
+
26
+
27
+ Here is the vector:
28
+
29
+
30
+ <concept>{injection_char}</concept>
31
+
32
+
33
+ Please provide an explanation.'
code-decoder/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec81cf4cd3712164ba5c12a479975a0d80142cf066a39632c941c5b40e569bff
3
+ size 11422917
code-decoder/tokenizer_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": null,
5
+ "clean_up_tokenization_spaces": false,
6
+ "eos_token": "<|im_end|>",
7
+ "errors": "replace",
8
+ "extra_special_tokens": [
9
+ "<|im_start|>",
10
+ "<|im_end|>",
11
+ "<|object_ref_start|>",
12
+ "<|object_ref_end|>",
13
+ "<|box_start|>",
14
+ "<|box_end|>",
15
+ "<|quad_start|>",
16
+ "<|quad_end|>",
17
+ "<|vision_start|>",
18
+ "<|vision_end|>",
19
+ "<|vision_pad|>",
20
+ "<|image_pad|>",
21
+ "<|video_pad|>"
22
+ ],
23
+ "is_local": false,
24
+ "model_max_length": 131072,
25
+ "pad_token": "<|endoftext|>",
26
+ "split_special_tokens": false,
27
+ "tokenizer_class": "Qwen2Tokenizer",
28
+ "unk_token": null
29
+ }
nla/actor/chat_template.jinja ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if tools %}
2
+ {{- '<|im_start|>system\n' }}
3
+ {%- if messages[0].role == 'system' %}
4
+ {{- messages[0].content + '\n\n' }}
5
+ {%- endif %}
6
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
7
+ {%- for tool in tools %}
8
+ {{- "\n" }}
9
+ {{- tool | tojson }}
10
+ {%- endfor %}
11
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
12
+ {%- else %}
13
+ {%- if messages[0].role == 'system' %}
14
+ {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
15
+ {%- endif %}
16
+ {%- endif %}
17
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
18
+ {%- for message in messages[::-1] %}
19
+ {%- set index = (messages|length - 1) - loop.index0 %}
20
+ {%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
21
+ {%- set ns.multi_step_tool = false %}
22
+ {%- set ns.last_query_index = index %}
23
+ {%- endif %}
24
+ {%- endfor %}
25
+ {%- for message in messages %}
26
+ {%- if message.content is string %}
27
+ {%- set content = message.content %}
28
+ {%- else %}
29
+ {%- set content = '' %}
30
+ {%- endif %}
31
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
32
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
33
+ {%- elif message.role == "assistant" %}
34
+ {%- set reasoning_content = '' %}
35
+ {%- if message.reasoning_content is string %}
36
+ {%- set reasoning_content = message.reasoning_content %}
37
+ {%- else %}
38
+ {%- if '</think>' in content %}
39
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
40
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
41
+ {%- endif %}
42
+ {%- endif %}
43
+ {%- if loop.index0 > ns.last_query_index %}
44
+ {%- if loop.last or (not loop.last and reasoning_content) %}
45
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
46
+ {%- else %}
47
+ {{- '<|im_start|>' + message.role + '\n' + content }}
48
+ {%- endif %}
49
+ {%- else %}
50
+ {{- '<|im_start|>' + message.role + '\n' + content }}
51
+ {%- endif %}
52
+ {%- if message.tool_calls %}
53
+ {%- for tool_call in message.tool_calls %}
54
+ {%- if (loop.first and content) or (not loop.first) %}
55
+ {{- '\n' }}
56
+ {%- endif %}
57
+ {%- if tool_call.function %}
58
+ {%- set tool_call = tool_call.function %}
59
+ {%- endif %}
60
+ {{- '<tool_call>\n{"name": "' }}
61
+ {{- tool_call.name }}
62
+ {{- '", "arguments": ' }}
63
+ {%- if tool_call.arguments is string %}
64
+ {{- tool_call.arguments }}
65
+ {%- else %}
66
+ {{- tool_call.arguments | tojson }}
67
+ {%- endif %}
68
+ {{- '}\n</tool_call>' }}
69
+ {%- endfor %}
70
+ {%- endif %}
71
+ {{- '<|im_end|>\n' }}
72
+ {%- elif message.role == "tool" %}
73
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
74
+ {{- '<|im_start|>user' }}
75
+ {%- endif %}
76
+ {{- '\n<tool_response>\n' }}
77
+ {{- content }}
78
+ {{- '\n</tool_response>' }}
79
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
80
+ {{- '<|im_end|>\n' }}
81
+ {%- endif %}
82
+ {%- endif %}
83
+ {%- endfor %}
84
+ {%- if add_generation_prompt %}
85
+ {{- '<|im_start|>assistant\n' }}
86
+ {%- if enable_thinking is defined and enable_thinking is false %}
87
+ {{- '<think>\n\n</think>\n\n' }}
88
+ {%- endif %}
89
+ {%- endif %}
nla/actor/config.json ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 151643,
8
+ "dtype": "float32",
9
+ "eos_token_id": 151645,
10
+ "head_dim": 128,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 4096,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 12288,
15
+ "layer_types": [
16
+ "full_attention",
17
+ "full_attention",
18
+ "full_attention",
19
+ "full_attention",
20
+ "full_attention",
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention",
44
+ "full_attention",
45
+ "full_attention",
46
+ "full_attention",
47
+ "full_attention",
48
+ "full_attention",
49
+ "full_attention",
50
+ "full_attention",
51
+ "full_attention"
52
+ ],
53
+ "max_position_embeddings": 40960,
54
+ "max_window_layers": 36,
55
+ "model_type": "qwen3",
56
+ "num_attention_heads": 32,
57
+ "num_hidden_layers": 36,
58
+ "num_key_value_heads": 8,
59
+ "pad_token_id": null,
60
+ "rms_norm_eps": 1e-06,
61
+ "rope_parameters": {
62
+ "rope_theta": 1000000,
63
+ "rope_type": "default"
64
+ },
65
+ "sliding_window": null,
66
+ "tie_word_embeddings": false,
67
+ "transformers_version": "5.3.0",
68
+ "use_cache": true,
69
+ "use_sliding_window": false,
70
+ "vocab_size": 151936
71
+ }
nla/actor/generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 151645,
6
+ 151643
7
+ ],
8
+ "pad_token_id": 151643,
9
+ "temperature": 0.6,
10
+ "top_k": 20,
11
+ "top_p": 0.95,
12
+ "transformers_version": "5.3.0"
13
+ }
nla/actor/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c0b6aac09dd63b70062b1966ad66a08ec82dc5531d47f6de4206ba4f97fe032
3
+ size 16381517208
nla/actor/nla_meta.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ kind: nla_dataset
2
+ schema_version: 2
3
+ dataset_id: av_sft_train.parquet
4
+ split_type: av_sft
5
+ row_count: 124741
6
+ extraction:
7
+ d_model: 4096
8
+ injection_scale: null
9
+ mse_scale: sqrt_d_model
10
+ norm: none
11
+ tokens:
12
+ injection_char: ㈎
13
+ injection_token_id: 149705
14
+ injection_left_neighbor_id: 29
15
+ injection_right_neighbor_id: 522
16
+ prompt_templates:
17
+ actor: 'You are a meticulous AI researcher conducting an important investigation
18
+ into activation vectors from a language model. Your overall task is to describe
19
+ the semantic content of that activation vector.
20
+
21
+
22
+ We will pass the vector enclosed in <concept> tags into your context. You must
23
+ then produce an explanation for the vector, enclosed within <explanation> tags.
24
+ The explanation consists of 2-3 text snippets describing that vector.
25
+
26
+
27
+ Here is the vector:
28
+
29
+
30
+ <concept>{injection_char}</concept>
31
+
32
+
33
+ Please provide an explanation.'
nla/actor/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:508635c756562e715417f4480d287917c332083cee37d2afca7f9b623fce55d9
3
+ size 11422916
nla/actor/tokenizer_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": null,
5
+ "clean_up_tokenization_spaces": false,
6
+ "eos_token": "<|im_end|>",
7
+ "errors": "replace",
8
+ "extra_special_tokens": [
9
+ "<|im_start|>",
10
+ "<|im_end|>",
11
+ "<|object_ref_start|>",
12
+ "<|object_ref_end|>",
13
+ "<|box_start|>",
14
+ "<|box_end|>",
15
+ "<|quad_start|>",
16
+ "<|quad_end|>",
17
+ "<|vision_start|>",
18
+ "<|vision_end|>",
19
+ "<|vision_pad|>",
20
+ "<|image_pad|>",
21
+ "<|video_pad|>"
22
+ ],
23
+ "is_local": false,
24
+ "model_max_length": 131072,
25
+ "pad_token": "<|endoftext|>",
26
+ "split_special_tokens": false,
27
+ "tokenizer_class": "Qwen2Tokenizer",
28
+ "unk_token": null
29
+ }
nla/critic/chat_template.jinja ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if tools %}
2
+ {{- '<|im_start|>system\n' }}
3
+ {%- if messages[0].role == 'system' %}
4
+ {{- messages[0].content + '\n\n' }}
5
+ {%- endif %}
6
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
7
+ {%- for tool in tools %}
8
+ {{- "\n" }}
9
+ {{- tool | tojson }}
10
+ {%- endfor %}
11
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
12
+ {%- else %}
13
+ {%- if messages[0].role == 'system' %}
14
+ {{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
15
+ {%- endif %}
16
+ {%- endif %}
17
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
18
+ {%- for message in messages[::-1] %}
19
+ {%- set index = (messages|length - 1) - loop.index0 %}
20
+ {%- if ns.multi_step_tool and message.role == "user" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
21
+ {%- set ns.multi_step_tool = false %}
22
+ {%- set ns.last_query_index = index %}
23
+ {%- endif %}
24
+ {%- endfor %}
25
+ {%- for message in messages %}
26
+ {%- if message.content is string %}
27
+ {%- set content = message.content %}
28
+ {%- else %}
29
+ {%- set content = '' %}
30
+ {%- endif %}
31
+ {%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
32
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
33
+ {%- elif message.role == "assistant" %}
34
+ {%- set reasoning_content = '' %}
35
+ {%- if message.reasoning_content is string %}
36
+ {%- set reasoning_content = message.reasoning_content %}
37
+ {%- else %}
38
+ {%- if '</think>' in content %}
39
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
40
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
41
+ {%- endif %}
42
+ {%- endif %}
43
+ {%- if loop.index0 > ns.last_query_index %}
44
+ {%- if loop.last or (not loop.last and reasoning_content) %}
45
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
46
+ {%- else %}
47
+ {{- '<|im_start|>' + message.role + '\n' + content }}
48
+ {%- endif %}
49
+ {%- else %}
50
+ {{- '<|im_start|>' + message.role + '\n' + content }}
51
+ {%- endif %}
52
+ {%- if message.tool_calls %}
53
+ {%- for tool_call in message.tool_calls %}
54
+ {%- if (loop.first and content) or (not loop.first) %}
55
+ {{- '\n' }}
56
+ {%- endif %}
57
+ {%- if tool_call.function %}
58
+ {%- set tool_call = tool_call.function %}
59
+ {%- endif %}
60
+ {{- '<tool_call>\n{"name": "' }}
61
+ {{- tool_call.name }}
62
+ {{- '", "arguments": ' }}
63
+ {%- if tool_call.arguments is string %}
64
+ {{- tool_call.arguments }}
65
+ {%- else %}
66
+ {{- tool_call.arguments | tojson }}
67
+ {%- endif %}
68
+ {{- '}\n</tool_call>' }}
69
+ {%- endfor %}
70
+ {%- endif %}
71
+ {{- '<|im_end|>\n' }}
72
+ {%- elif message.role == "tool" %}
73
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
74
+ {{- '<|im_start|>user' }}
75
+ {%- endif %}
76
+ {{- '\n<tool_response>\n' }}
77
+ {{- content }}
78
+ {{- '\n</tool_response>' }}
79
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
80
+ {{- '<|im_end|>\n' }}
81
+ {%- endif %}
82
+ {%- endif %}
83
+ {%- endfor %}
84
+ {%- if add_generation_prompt %}
85
+ {{- '<|im_start|>assistant\n' }}
86
+ {%- if enable_thinking is defined and enable_thinking is false %}
87
+ {{- '<think>\n\n</think>\n\n' }}
88
+ {%- endif %}
89
+ {%- endif %}
nla/critic/config.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3ForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 151643,
8
+ "dtype": "float32",
9
+ "eos_token_id": 151645,
10
+ "head_dim": 128,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 4096,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 12288,
15
+ "layer_types": [
16
+ "full_attention",
17
+ "full_attention",
18
+ "full_attention",
19
+ "full_attention",
20
+ "full_attention",
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention"
41
+ ],
42
+ "max_position_embeddings": 40960,
43
+ "max_window_layers": 36,
44
+ "model_type": "qwen3",
45
+ "num_attention_heads": 32,
46
+ "num_hidden_layers": 25,
47
+ "num_key_value_heads": 8,
48
+ "pad_token_id": null,
49
+ "rms_norm_eps": 1e-06,
50
+ "rope_parameters": {
51
+ "rope_theta": 1000000,
52
+ "rope_type": "default"
53
+ },
54
+ "sliding_window": null,
55
+ "tie_word_embeddings": false,
56
+ "transformers_version": "5.3.0",
57
+ "use_cache": true,
58
+ "use_sliding_window": false,
59
+ "vocab_size": 151936
60
+ }
nla/critic/generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 151645,
6
+ 151643
7
+ ],
8
+ "pad_token_id": 151643,
9
+ "temperature": 0.6,
10
+ "top_k": 20,
11
+ "top_p": 0.95,
12
+ "transformers_version": "5.3.0"
13
+ }
nla/critic/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:64ccdb33deb4e857f61d79b2b430827cdc7422b97bb26ed30c15023b749710d4
3
+ size 10892013280
nla/critic/nla_meta.yaml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ kind: nla_dataset
2
+ schema_version: 2
3
+ dataset_id: ar_sft_train.parquet
4
+ split_type: ar_sft
5
+ row_count: 124708
6
+ extraction:
7
+ d_model: 4096
8
+ injection_scale: null
9
+ mse_scale: sqrt_d_model
10
+ norm: none
11
+ tokens:
12
+ injection_char: ㈎
13
+ injection_token_id: 149705
14
+ injection_left_neighbor_id: 29
15
+ injection_right_neighbor_id: 522
16
+ critic_suffix_ids:
17
+ - 1318
18
+ - 29
19
+ - 366
20
+ - 1708
21
+ - 29
22
+ prompt_templates:
23
+ actor: 'You are a meticulous AI researcher conducting an important investigation
24
+ into activation vectors from a language model. Your overall task is to describe
25
+ the semantic content of that activation vector.
26
+
27
+
28
+ We will pass the vector enclosed in <concept> tags into your context. You must
29
+ then produce an explanation for the vector, enclosed within <explanation> tags.
30
+ The explanation consists of 2-3 text snippets describing that vector.
31
+
32
+
33
+ Here is the vector:
34
+
35
+
36
+ <concept>{injection_char}</concept>
37
+
38
+
39
+ Please provide an explanation.'
40
+ critic: 'Summary of the following text: <text>{explanation}</text> <summary>'
nla/critic/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:508635c756562e715417f4480d287917c332083cee37d2afca7f9b623fce55d9
3
+ size 11422916
nla/critic/tokenizer_config.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "backend": "tokenizers",
4
+ "bos_token": null,
5
+ "clean_up_tokenization_spaces": false,
6
+ "eos_token": "<|im_end|>",
7
+ "errors": "replace",
8
+ "extra_special_tokens": [
9
+ "<|im_start|>",
10
+ "<|im_end|>",
11
+ "<|object_ref_start|>",
12
+ "<|object_ref_end|>",
13
+ "<|box_start|>",
14
+ "<|box_end|>",
15
+ "<|quad_start|>",
16
+ "<|quad_end|>",
17
+ "<|vision_start|>",
18
+ "<|vision_end|>",
19
+ "<|vision_pad|>",
20
+ "<|image_pad|>",
21
+ "<|video_pad|>"
22
+ ],
23
+ "is_local": true,
24
+ "model_max_length": 131072,
25
+ "pad_token": "<|endoftext|>",
26
+ "split_special_tokens": false,
27
+ "tokenizer_class": "Qwen2Tokenizer",
28
+ "unk_token": null
29
+ }
nla/critic/value_head.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7c723c4a55baf24ee2831146c25dcf0b08b256c1ebd90a05a2bea326d445484c
3
+ size 33554520
nla_code_demo.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Standalone demo for the tiny_nla code decoder. No repo checkout needed.
3
+
4
+ pip install torch transformers huggingface_hub numpy pyyaml
5
+ hf auth login
6
+ hf download ASSERT-KTH/tiny-nla nla_code_demo.py --local-dir .
7
+ python nla_code_demo.py --code 'def is_palindrome(s):
8
+ s = s.lower()
9
+ return s == s[::-1]'
10
+
11
+ Encodes a Python function to ONE 4096-float activation vector with the frozen
12
+ base model, injects that vector into the trained decoder, and asks it to write
13
+ the function back out.
14
+
15
+ This is not a lossless codec. On a 300-function held-out set: 1% exact, 7.7%
16
+ structurally near-identical, 71% retrieval@1 (is the output nearest its own
17
+ target among 300). The typical error is a SIBLING -- ask for send_video and get
18
+ send_document, same signature, wrong entity. Always run with --control, which
19
+ generates from a random vector so you can see what the code prior alone gives.
20
+
21
+ Needs one GPU with >=24 GB. The two models load sequentially, not together.
22
+ """
23
+ import argparse, ast, builtins, difflib, keyword, re, sys
24
+ from pathlib import Path
25
+ import numpy as np, torch, yaml
26
+ from huggingface_hub import hf_hub_download
27
+ from transformers import AutoModelForCausalLM, AutoTokenizer
28
+
29
+ CODE_RE = re.compile(r"<code>\s*(.*?)\s*</code>", re.DOTALL)
30
+ STOP = set(keyword.kwlist) | set(dir(builtins)) | {"self", "cls"}
31
+
32
+
33
+ def inject_at_marked_positions(input_ids, embeddings, vectors, inj_id, left_id, right_id):
34
+ """Overwrite embedding rows at injection markers. Copied verbatim from
35
+ nla/training/injection.py -- the most correctness-critical path: if
36
+ injection fails the model sees the literal marker char and emits Chinese.
37
+ The neighbour check prevents false positives from the marker appearing in
38
+ generated text."""
39
+ out = embeddings.clone()
40
+ vectors = vectors.to(out.device, out.dtype)
41
+ seq_len = input_ids.shape[-1]
42
+ vec_idx = 0
43
+ for b, p in (input_ids == inj_id).nonzero(as_tuple=False).tolist():
44
+ if p == 0 or p == seq_len - 1:
45
+ continue
46
+ if input_ids[b, p - 1] != left_id or input_ids[b, p + 1] != right_id:
47
+ continue
48
+ out[b, p] = vectors[vec_idx]
49
+ vec_idx += 1
50
+ if vec_idx != vectors.shape[0]:
51
+ raise RuntimeError(f"found {vec_idx} injection sites, expected {vectors.shape[0]}")
52
+ return out
53
+
54
+
55
+ def normalize_activation(v, target_scale):
56
+ if target_scale is None:
57
+ return v
58
+ return v / (v.float().norm(dim=-1, keepdim=True).clamp_min(1e-12) / target_scale).to(v.dtype)
59
+
60
+
61
+ def canon(src):
62
+ try:
63
+ return ast.unparse(ast.parse(src))
64
+ except SyntaxError as e:
65
+ sys.exit(f"input is not valid Python: {e}")
66
+
67
+
68
+ def skeleton(src):
69
+ try:
70
+ return [type(n).__name__ for n in ast.walk(ast.parse(src))]
71
+ except SyntaxError:
72
+ return []
73
+
74
+
75
+ def idents(src):
76
+ try:
77
+ t = ast.parse(src)
78
+ except SyntaxError:
79
+ return set()
80
+ o = set()
81
+ for n in ast.walk(t):
82
+ for a in ("id", "arg", "name", "attr"):
83
+ v = getattr(n, a, None)
84
+ if isinstance(v, str) and v not in STOP and not v.startswith("__"):
85
+ o.add(v)
86
+ return o
87
+
88
+
89
+ def main():
90
+ p = argparse.ArgumentParser(description="reconstruct a Python function from its activation vector")
91
+ g = p.add_mutually_exclusive_group(required=True)
92
+ g.add_argument("--code", help="function source")
93
+ g.add_argument("--file", help="path to a .py file")
94
+ p.add_argument("--repo", default="ASSERT-KTH/tiny-nla")
95
+ p.add_argument("--subfolder", default="code-decoder")
96
+ p.add_argument("--base-model", default="Qwen/Qwen3-8B")
97
+ p.add_argument("--injection-scale", type=float, default=300.0)
98
+ p.add_argument("--max-new-tokens", type=int, default=256)
99
+ p.add_argument("--control", action="store_true",
100
+ help="also generate from a random vector -- the code prior with no information")
101
+ p.add_argument("--device", default="cuda")
102
+ a = p.parse_args()
103
+
104
+ code = canon(Path(a.file).read_text() if a.file else a.code)
105
+
106
+ meta = yaml.safe_load(open(hf_hub_download(a.repo, f"{a.subfolder}/nla_meta.yaml")))["tokens"]
107
+ inj, L, R, ch = (meta["injection_token_id"], meta["injection_left_neighbor_id"],
108
+ meta["injection_right_neighbor_id"], meta["injection_char"])
109
+ # A centred decoder given a raw vector produces fluent, plausible, entirely
110
+ # unrelated code with no error. Fetch the mean rather than trust the caller.
111
+ try:
112
+ mu = np.load(hf_hub_download(a.repo, f"{a.subfolder}/centre_mean.npy")).reshape(-1)
113
+ except Exception:
114
+ mu = None
115
+ print("note: no centre_mean.npy in repo -- treating decoder as raw-vector")
116
+
117
+ tok = AutoTokenizer.from_pretrained(a.base_model)
118
+ if tok.pad_token_id is None:
119
+ tok.pad_token_id = tok.eos_token_id
120
+
121
+ base = AutoModelForCausalLM.from_pretrained(
122
+ a.base_model, torch_dtype=torch.bfloat16, device_map={"": a.device}).eval()
123
+ layer = (2 * len(base.model.layers)) // 3
124
+ cap = {}
125
+ h = base.model.layers[layer].register_forward_hook(
126
+ lambda _m, _i, o: cap.__setitem__("h", (o[0] if isinstance(o, tuple) else o).detach()))
127
+ with torch.no_grad():
128
+ e = tok(code, return_tensors="pt", truncation=True, max_length=1024)
129
+ ids, m = e["input_ids"].to(a.device), e["attention_mask"].to(a.device)
130
+ base(input_ids=ids, attention_mask=m, use_cache=False)
131
+ h.remove()
132
+ v = cap["h"][0, int(m.sum()) - 1].float().cpu().numpy()
133
+ print(f"encoded {int(m.sum())} tokens at layer {layer} ‖v‖ = {np.linalg.norm(v):.1f}")
134
+ if mu is not None:
135
+ v = v - mu
136
+ print(f"centred (‖μ‖ = {np.linalg.norm(mu):.1f})")
137
+ del base
138
+ torch.cuda.empty_cache()
139
+
140
+ actor = AutoModelForCausalLM.from_pretrained(
141
+ a.repo, subfolder=a.subfolder, torch_dtype=torch.bfloat16,
142
+ device_map={"": a.device}).eval()
143
+ embed = actor.get_input_embeddings()
144
+
145
+ def gen(vec):
146
+ bv = normalize_activation(torch.tensor(vec).unsqueeze(0), a.injection_scale).to(a.device)
147
+ s = tok.apply_chat_template([{"role": "user", "content": f"<concept>{ch}</concept>"}],
148
+ tokenize=False, add_generation_prompt=True)
149
+ enc = tok(s, return_tensors="pt", add_special_tokens=False)
150
+ i2, m2 = enc["input_ids"].to(a.device), enc["attention_mask"].to(a.device)
151
+ hk = embed.register_forward_hook(
152
+ lambda _m, i, o: o if o.shape[1] <= 1 else inject_at_marked_positions(i[0], o, bv, inj, L, R))
153
+ try:
154
+ with torch.no_grad():
155
+ out = actor.generate(i2, attention_mask=m2, max_new_tokens=a.max_new_tokens,
156
+ do_sample=False, pad_token_id=tok.pad_token_id,
157
+ eos_token_id=tok.eos_token_id)
158
+ finally:
159
+ hk.remove()
160
+ mm = CODE_RE.search(tok.decode(out[0][i2.shape[1]:], skip_special_tokens=True))
161
+ return mm.group(1) if mm else None
162
+
163
+ pred = gen(v)
164
+ bar = "=" * 70
165
+ print(f"\n{bar}\nORIGINAL\n{bar}\n{code}")
166
+ print(f"{bar}\nRECONSTRUCTED\n{bar}\n{pred or '(no <code> block emitted)'}")
167
+ if pred:
168
+ pu, cu = idents(pred), idents(code)
169
+ print(bar)
170
+ print(f"exact match : {pred.strip() == code.strip()}")
171
+ print(f"AST-skeleton sim : {difflib.SequenceMatcher(None, skeleton(pred), skeleton(code)).ratio():.3f}"
172
+ f" (~0.52 for two arbitrary functions)")
173
+ print(f"identifier recall : {len(pu & cu) / max(len(cu), 1):.2f} shared: {sorted(pu & cu)[:8]}")
174
+ if a.control:
175
+ rng = np.random.default_rng(0)
176
+ print(f"{bar}\nCONTROL (random vector -- the code prior with no information)\n{bar}")
177
+ print(gen(rng.normal(0, np.std(v), size=v.shape).astype(np.float32)) or "(none)")
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
nla_demo.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """tiny_nla demo — one entry point, no model to choose.
3
+
4
+ pip install torch transformers accelerate huggingface_hub numpy pyyaml
5
+ hf auth login
6
+ hf download ASSERT-KTH/tiny-nla nla_demo.py --local-dir .
7
+
8
+ python nla_demo.py --mode code 'def gcd(a, b):
9
+ while b: a, b = b, a % b
10
+ return a' # -> reconstructs the function
11
+
12
+ python nla_demo.py --mode text 'The Federal Reserve announced yesterday'
13
+ # -> explains the activation
14
+
15
+ python nla_demo.py --mode code --file mymodule.py --control
16
+
17
+ Compresses the input to ONE 4096-float activation vector from a frozen
18
+ Qwen3-8B, then asks a trained decoder to write it back out. YOU pick the mode;
19
+ the CHECKPOINT is chosen for you -- always whatever currently sits at the
20
+ pinned Hub paths, with no flag to select an older one.
21
+
22
+ Needs one GPU with >=24 GB. Models load sequentially, not together.
23
+ """
24
+ import argparse, ast, builtins, difflib, keyword, os, re, shutil, subprocess, sys, tempfile
25
+ from pathlib import Path
26
+
27
+ # HF_HOME must be set BEFORE huggingface_hub is imported -- it snapshots the
28
+ # cache location at import time, so doing this inside main() is too late. When
29
+ # the user has not chosen one, prefer this repo's cache: the weights are very
30
+ # likely already there, and the default (~/.cache) would re-download ~33 GB
31
+ # into a home directory that may not have the quota for it.
32
+ if not os.environ.get("HF_HOME"):
33
+ _repo_cache = Path(__file__).resolve().parent.parent / "cache" / "huggingface"
34
+ if _repo_cache.is_dir():
35
+ os.environ["HF_HOME"] = str(_repo_cache)
36
+
37
+ import numpy as np, torch, transformers, yaml
38
+ from huggingface_hub import hf_hub_download
39
+ from transformers import AutoModelForCausalLM, AutoTokenizer
40
+
41
+ REPO = "ASSERT-KTH/tiny-nla"
42
+ CODE_SUB, TEXT_SUB = "code-decoder", "nla/actor"
43
+ BASE = "Qwen/Qwen3-8B"
44
+ # Verified per mode by A/B (job 17444219) -- do NOT "unify" these.
45
+ # text + minimal -> emits <answer>, drifts off-topic (regex misses, raw falls through)
46
+ # code + sidecar -> ident recall 0.00: writes a function ABOUT activation vectors,
47
+ # following the preamble's subject instead of the injected vector
48
+ # the winners -> code 1.00 recall / AST 0.862 ; text = correct 2-3 snippet format
49
+ # Each checkpoint wants the prompt IT was trained on. code-decoder's sidecar
50
+ # prompt_templates is a stale dataset-builder default and does not record that.
51
+ PROMPT_MODE = {"code": "minimal", "text": "sidecar"}
52
+
53
+ # transformers 5.x renamed torch_dtype -> dtype and warns on the old name; 4.x
54
+ # does not know the new one. An unpinned `pip install transformers` gets 5.x.
55
+ _DT = ({"dtype": torch.bfloat16}
56
+ if int(transformers.__version__.split(".")[0]) >= 5
57
+ else {"torch_dtype": torch.bfloat16})
58
+ CODE_RE = re.compile(r"<code>\s*(.*?)\s*</code>", re.DOTALL)
59
+ EXPL_RE = re.compile(r"<explanation>\s*(.*?)\s*</explanation>", re.DOTALL)
60
+ STOP = set(keyword.kwlist) | set(dir(builtins)) | {"self", "cls"}
61
+
62
+
63
+ def inject_at_marked_positions(input_ids, embeddings, vectors, inj_id, left_id, right_id):
64
+ """Verbatim from nla/training/injection.py. The neighbour check prevents
65
+ false positives; if injection silently fails the model emits Chinese."""
66
+ out = embeddings.clone()
67
+ vectors = vectors.to(out.device, out.dtype)
68
+ n, k = input_ids.shape[-1], 0
69
+ for b, p in (input_ids == inj_id).nonzero(as_tuple=False).tolist():
70
+ if p == 0 or p == n - 1:
71
+ continue
72
+ if input_ids[b, p - 1] != left_id or input_ids[b, p + 1] != right_id:
73
+ continue
74
+ out[b, p] = vectors[k]; k += 1
75
+ if k != vectors.shape[0]:
76
+ raise RuntimeError(f"found {k} injection sites, expected {vectors.shape[0]}")
77
+ return out
78
+
79
+
80
+ def normalize_activation(v, scale):
81
+ return v / (v.float().norm(dim=-1, keepdim=True).clamp_min(1e-12) / scale).to(v.dtype)
82
+
83
+
84
+ def scratch_dir():
85
+ """A writable scratch directory that is never /tmp.
86
+
87
+ /tmp is off limits on this cluster and is node-local anyway, but both
88
+ tempfile and triton default there. Honour an explicit TMPDIR when it does
89
+ not point into /tmp, else fall back to the user's cache dir.
90
+ """
91
+ for cand in (os.environ.get("TMPDIR"), os.environ.get("XDG_CACHE_HOME")):
92
+ if cand and not os.path.realpath(cand).startswith("/tmp"):
93
+ d = os.path.join(cand, "tiny_nla")
94
+ try:
95
+ os.makedirs(d, exist_ok=True)
96
+ return d
97
+ except OSError:
98
+ pass
99
+ d = os.path.join(os.path.expanduser("~"), ".cache", "tiny_nla")
100
+ os.makedirs(d, exist_ok=True)
101
+ return d
102
+
103
+
104
+ def ensure_compiler():
105
+ """Pick a C compiler that actually works, before torch needs one.
106
+
107
+ Triton JIT-compiles a CUDA helper on first GPU use and shells out to `cc`.
108
+ On NSC/Berzelius the gcc first on PATH is a wrapper that refuses to run
109
+ without a build-env module loaded; the refusal surfaces as a
110
+ CalledProcessError fifteen frames deep inside triton, naming a .c file the
111
+ user never wrote. Probe candidates once and export the first that links.
112
+ Costs one ~100ms compile, and is skipped entirely if CC is already set.
113
+ """
114
+ if os.environ.get("CC"):
115
+ return
116
+ with tempfile.TemporaryDirectory(dir=scratch_dir()) as d:
117
+ src = os.path.join(d, "probe.c")
118
+ with open(src, "w") as f:
119
+ f.write("int main(){return 0;}")
120
+ for cand in (shutil.which("cc"), shutil.which("gcc"), "/usr/bin/gcc"):
121
+ if not cand or not os.path.exists(cand):
122
+ continue
123
+ try:
124
+ r = subprocess.run([cand, "-shared", "-fPIC", "-o",
125
+ os.path.join(d, "probe.so"), src],
126
+ capture_output=True, timeout=60)
127
+ except Exception:
128
+ continue
129
+ if r.returncode == 0:
130
+ os.environ["CC"] = cand
131
+ return
132
+
133
+
134
+ def parses(s):
135
+ try:
136
+ ast.parse(s); return True
137
+ except SyntaxError:
138
+ return False
139
+
140
+
141
+ # Keywords of the curly-brace languages, so a lexical scan of non-Python source
142
+ # does not report `int` or `return` as recovered identifiers.
143
+ OTHER_KW = {"int", "float", "double", "char", "bool", "void", "long", "short",
144
+ "unsigned", "signed", "const", "static", "struct", "class", "public",
145
+ "private", "protected", "return", "if", "else", "for", "while", "do",
146
+ "switch", "case", "break", "continue", "new", "delete", "using",
147
+ "namespace", "template", "typename", "include", "std", "auto", "let",
148
+ "var", "function", "def", "fn", "pub", "impl", "final", "extends"}
149
+
150
+
151
+ def idents(src):
152
+ """Identifiers in `src`, Python-aware when possible, lexical otherwise."""
153
+ try:
154
+ t = ast.parse(src)
155
+ except SyntaxError:
156
+ return {w for w in re.findall(r"[A-Za-z_][A-Za-z0-9_]*", src)
157
+ if w not in STOP and w not in OTHER_KW and not w.startswith("__")}
158
+ return {v for n in ast.walk(t) for a in ("id", "arg", "name", "attr")
159
+ if isinstance(v := getattr(n, a, None), str) and v not in STOP and not v.startswith("__")}
160
+
161
+
162
+ def main():
163
+ p = argparse.ArgumentParser(description="tiny_nla demo — compress to one vector, decode it back")
164
+ p.add_argument("--prompt", choices=["minimal", "sidecar"], default=None,
165
+ help="override the injection prompt (diagnostic; default is per-mode verified)")
166
+ p.add_argument("--mode", required=True, choices=["code", "text"],
167
+ help="code = reconstruct a Python function; text = explain the activation")
168
+ p.add_argument("input", nargs="?", help="Python function, or any text")
169
+ p.add_argument("--file", help="read the input from a file instead")
170
+ p.add_argument("--control", action="store_true",
171
+ help="also decode a RANDOM vector — what the model produces with no information")
172
+ p.add_argument("--max-new-tokens", type=int, default=256)
173
+ p.add_argument("--device", default="cuda")
174
+ a = p.parse_args()
175
+ if not a.input and not a.file:
176
+ p.error("give some input, or --file")
177
+ raw = Path(a.file).read_text() if a.file else a.input
178
+
179
+ code_mode = a.mode == "code"
180
+ if code_mode and parses(raw):
181
+ raw = ast.unparse(ast.parse(raw)) # canonicalise so formatting is not counted
182
+ elif code_mode:
183
+ # Not Python: encode and decode it anyway. Only the AST-based scoring
184
+ # needs a parse, so it degrades rather than refusing. The DECODER was
185
+ # trained on Python only, so treat any other language as exploratory.
186
+ print("note: input is not valid Python -- skipping canonicalisation and "
187
+ "AST scoring. The decoder is Python-trained; other languages are "
188
+ "out of distribution.")
189
+ # Keep triton's JIT scratch and every tempfile default off /tmp.
190
+ _sd = scratch_dir()
191
+ if os.path.realpath(os.environ.get("TMPDIR", "/tmp")).startswith("/tmp"):
192
+ os.environ["TMPDIR"] = _sd
193
+ tempfile.tempdir = _sd
194
+ os.environ.setdefault("TRITON_CACHE_DIR", os.path.join(_sd, "triton"))
195
+ os.environ.setdefault("TORCHINDUCTOR_CACHE_DIR", os.path.join(_sd, "inductor"))
196
+ ensure_compiler()
197
+ sub = CODE_SUB if code_mode else TEXT_SUB
198
+ print(f"mode: {'CODE reconstruction' if code_mode else 'TEXT explanation'} "
199
+ f"(checkpoint chosen automatically: {REPO}/{sub})")
200
+
201
+ try:
202
+ sc = yaml.safe_load(open(hf_hub_download(REPO, f"{sub}/nla_meta.yaml")))
203
+ except Exception as e:
204
+ if "401" in str(e) or "Repository Not Found" in str(e):
205
+ sys.exit(
206
+ f"cannot read {REPO} (401).\n"
207
+ f" HF_HOME = {os.environ.get('HF_HOME', '<unset, using ~/.cache/huggingface>')}\n"
208
+ f" token = {'found' if os.path.exists(os.path.join(os.environ.get('HF_HOME', os.path.expanduser('~/.cache/huggingface')), 'token')) else 'NOT FOUND in that HF_HOME'}\n"
209
+ f" The usual cause is HF_HOME pointing at a cache with no token,\n"
210
+ f" not a missing account. Point it at the cache you logged into:\n"
211
+ f" export HF_HOME=/path/to/cache/huggingface\n"
212
+ f" Otherwise run `hf auth login`, and ask the repo owner for access.")
213
+ raise
214
+ meta = sc["tokens"]
215
+ inj, L, R, ch = (meta["injection_token_id"], meta["injection_left_neighbor_id"],
216
+ meta["injection_right_neighbor_id"], meta["injection_char"])
217
+ try: # a centred decoder given a raw vector yields fluent, unrelated output
218
+ mu = np.load(hf_hub_download(REPO, f"{sub}/centre_mean.npy")).reshape(-1)
219
+ except Exception:
220
+ mu = None
221
+
222
+ tok = AutoTokenizer.from_pretrained(BASE)
223
+ if tok.pad_token_id is None:
224
+ tok.pad_token_id = tok.eos_token_id
225
+
226
+ base = AutoModelForCausalLM.from_pretrained(
227
+ BASE, **_DT, device_map={"": a.device}).eval()
228
+ layer = (2 * len(base.model.layers)) // 3
229
+ cap = {}
230
+ h = base.model.layers[layer].register_forward_hook(
231
+ lambda _m, _i, o: cap.__setitem__("h", (o[0] if isinstance(o, tuple) else o).detach()))
232
+ with torch.no_grad():
233
+ e = tok(raw, return_tensors="pt", truncation=True, max_length=1024)
234
+ ids, m = e["input_ids"].to(a.device), e["attention_mask"].to(a.device)
235
+ base(input_ids=ids, attention_mask=m, use_cache=False)
236
+ h.remove()
237
+ v = cap["h"][0, int(m.sum()) - 1].float().cpu().numpy()
238
+ print(f"encoded {int(m.sum())} tokens at layer {layer} ‖v‖={np.linalg.norm(v):.1f}"
239
+ + (f" centred (‖μ‖={np.linalg.norm(mu):.1f})" if mu is not None else ""))
240
+ if mu is not None:
241
+ v = v - mu
242
+ del base; torch.cuda.empty_cache()
243
+
244
+ actor = AutoModelForCausalLM.from_pretrained(
245
+ REPO, subfolder=sub, **_DT, device_map={"": a.device}).eval()
246
+ embed = actor.get_input_embeddings()
247
+
248
+ # Which prompt a checkpoint wants is a property of how it was TRAINED, and the
249
+ # sidecar's prompt_templates is a dataset-builder default that does not always
250
+ # match. Both were measured; see PROMPT_MODE.
251
+ want = a.prompt or PROMPT_MODE[a.mode]
252
+ tmpl = (sc.get("prompt_templates") or {}).get("actor") if want == "sidecar" else None
253
+ user_msg = tmpl.replace("{injection_char}", ch) if tmpl else f"<concept>{ch}</concept>"
254
+
255
+ def gen(vec):
256
+ bv = normalize_activation(torch.tensor(vec).unsqueeze(0), 300.0).to(a.device)
257
+ s = tok.apply_chat_template([{"role": "user", "content": user_msg}],
258
+ tokenize=False, add_generation_prompt=True)
259
+ enc = tok(s, return_tensors="pt", add_special_tokens=False)
260
+ i2, m2 = enc["input_ids"].to(a.device), enc["attention_mask"].to(a.device)
261
+ hk = embed.register_forward_hook(
262
+ lambda _m, i, o: o if o.shape[1] <= 1 else inject_at_marked_positions(i[0], o, bv, inj, L, R))
263
+ try:
264
+ with torch.no_grad():
265
+ out = actor.generate(i2, attention_mask=m2, max_new_tokens=a.max_new_tokens,
266
+ do_sample=False, pad_token_id=tok.pad_token_id,
267
+ eos_token_id=tok.eos_token_id)
268
+ finally:
269
+ hk.remove()
270
+ txt = tok.decode(out[0][i2.shape[1]:], skip_special_tokens=True)
271
+ mm = (CODE_RE if code_mode else EXPL_RE).search(txt)
272
+ return mm.group(1) if mm else txt.strip()[:600]
273
+
274
+ bar = "=" * 70
275
+ pred = gen(v)
276
+ print(f"\n{bar}\nINPUT\n{bar}\n{raw[:600]}")
277
+ print(f"{bar}\n{'RECONSTRUCTED' if code_mode else 'EXPLANATION'}\n{bar}\n{pred}")
278
+ if code_mode and pred:
279
+ pu, cu = idents(pred), idents(raw)
280
+ def _shape(x):
281
+ return [type(n).__name__ for n in ast.walk(ast.parse(x))]
282
+ sk = (difflib.SequenceMatcher(None, _shape(pred), _shape(raw)).ratio()
283
+ if parses(pred) and parses(raw) else None)
284
+ print(bar)
285
+ print(f"exact match : {pred.strip() == raw.strip()}")
286
+ print(f"AST-skeleton sim : "
287
+ + (f"{sk:.3f} (~0.52 for two arbitrary functions)" if sk is not None
288
+ else "n/a (needs both sides to be valid Python)"))
289
+ print(f"identifier recall : {len(pu & cu)/max(len(cu),1):.2f} shared: {sorted(pu & cu)[:8]}")
290
+ if a.control:
291
+ rng = np.random.default_rng(0)
292
+ print(f"{bar}\nCONTROL — random vector, i.e. the model's prior with no information\n{bar}")
293
+ print(gen(rng.normal(0, np.std(v), size=v.shape).astype(np.float32)))
294
+
295
+
296
+ if __name__ == "__main__":
297
+ main()