keepsloading commited on
Commit
1539e1c
·
verified ·
1 Parent(s): cdb6587

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. outputs/eval/logs/vanilla/ruler_a.log +42 -0
  3. outputs/l2a_style/hotstart/added_tokens.json +28 -0
  4. outputs/l2a_style/hotstart/aha_hotstart_manifest.json +14 -0
  5. outputs/l2a_style/hotstart/chat_template.jinja +89 -0
  6. outputs/l2a_style/hotstart/config.json +79 -0
  7. outputs/l2a_style/hotstart/generation_config.json +7 -0
  8. outputs/l2a_style/hotstart/merges.txt +0 -0
  9. outputs/l2a_style/hotstart/model-00001-of-00002.safetensors +3 -0
  10. outputs/l2a_style/hotstart/model-00002-of-00002.safetensors +3 -0
  11. outputs/l2a_style/hotstart/model.safetensors.index.json +431 -0
  12. outputs/l2a_style/hotstart/modeling_aha_qwen3.py +1226 -0
  13. outputs/l2a_style/hotstart/special_tokens_map.json +31 -0
  14. outputs/l2a_style/hotstart/tokenizer.json +3 -0
  15. outputs/l2a_style/hotstart/tokenizer_config.json +239 -0
  16. outputs/l2a_style/hotstart/vocab.json +0 -0
  17. outputs/l2a_style/logs/hotstart.log +2 -0
  18. outputs/l2a_style/logs/stage1.log +40 -0
  19. outputs/l2a_style/logs/stage2.log +125 -0
  20. outputs/l2a_style/stage1/checkpoint-100/added_tokens.json +28 -0
  21. outputs/l2a_style/stage1/checkpoint-100/chat_template.jinja +89 -0
  22. outputs/l2a_style/stage1/checkpoint-100/config.json +79 -0
  23. outputs/l2a_style/stage1/checkpoint-100/dynamic_duo_state.json +17 -0
  24. outputs/l2a_style/stage1/checkpoint-100/dynamic_duo_train_args.json +36 -0
  25. outputs/l2a_style/stage1/checkpoint-100/generation_config.json +7 -0
  26. outputs/l2a_style/stage1/checkpoint-100/merges.txt +0 -0
  27. outputs/l2a_style/stage1/checkpoint-100/model.safetensors +3 -0
  28. outputs/l2a_style/stage1/checkpoint-100/modeling_aha_qwen3.py +1226 -0
  29. outputs/l2a_style/stage1/checkpoint-100/special_tokens_map.json +31 -0
  30. outputs/l2a_style/stage1/checkpoint-100/tokenizer.json +3 -0
  31. outputs/l2a_style/stage1/checkpoint-100/tokenizer_config.json +239 -0
  32. outputs/l2a_style/stage1/checkpoint-100/vocab.json +0 -0
  33. outputs/l2a_style/stage1/checkpoint-200/added_tokens.json +28 -0
  34. outputs/l2a_style/stage1/checkpoint-200/chat_template.jinja +89 -0
  35. outputs/l2a_style/stage1/checkpoint-200/config.json +79 -0
  36. outputs/l2a_style/stage1/checkpoint-200/dynamic_duo_state.json +17 -0
  37. outputs/l2a_style/stage1/checkpoint-200/dynamic_duo_train_args.json +36 -0
  38. outputs/l2a_style/stage1/checkpoint-200/generation_config.json +7 -0
  39. outputs/l2a_style/stage1/checkpoint-200/merges.txt +0 -0
  40. outputs/l2a_style/stage1/checkpoint-200/model.safetensors +3 -0
  41. outputs/l2a_style/stage1/checkpoint-200/modeling_aha_qwen3.py +1226 -0
  42. outputs/l2a_style/stage1/checkpoint-200/special_tokens_map.json +31 -0
  43. outputs/l2a_style/stage1/checkpoint-200/tokenizer.json +3 -0
  44. outputs/l2a_style/stage1/checkpoint-200/tokenizer_config.json +239 -0
  45. outputs/l2a_style/stage1/checkpoint-200/vocab.json +0 -0
  46. outputs/l2a_style/stage1/checkpoint-300/added_tokens.json +28 -0
  47. outputs/l2a_style/stage1/checkpoint-300/chat_template.jinja +89 -0
  48. outputs/l2a_style/stage1/checkpoint-300/config.json +79 -0
  49. outputs/l2a_style/stage1/checkpoint-300/dynamic_duo_state.json +17 -0
  50. outputs/l2a_style/stage1/checkpoint-300/dynamic_duo_train_args.json +36 -0
.gitattributes CHANGED
@@ -2,3 +2,6 @@
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.jsonl filter=lfs diff=lfs merge=lfs -text
4
  tokenizer.json filter=lfs diff=lfs merge=lfs -text
 
 
 
 
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.jsonl filter=lfs diff=lfs merge=lfs -text
4
  tokenizer.json filter=lfs diff=lfs merge=lfs -text
5
+ outputs/l2a_style/stage2/checkpoint-25/optimizer.pt filter=lfs diff=lfs merge=lfs -text
6
+ outputs/l2a_style/stage2/checkpoint-50/optimizer.pt filter=lfs diff=lfs merge=lfs -text
7
+ outputs/l2a_style/stage2/checkpoint-75/optimizer.pt filter=lfs diff=lfs merge=lfs -text
outputs/eval/logs/vanilla/ruler_a.log ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2026-07-18:09:44:52 WARNING [config.evaluate_config:281] --limit SHOULD ONLY BE USED FOR TESTING. REAL METRICS SHOULD NOT BE COMPUTED USING LIMIT.
2
+ 2026-07-18:09:44:56 INFO [_cli.run:376] Selected Tasks: ['niah_single_1', 'niah_single_3', 'niah_multikey_2', 'niah_multiquery', 'ruler_vt', 'ruler_fwe', 'ruler_qa_hotpot']
3
+ 2026-07-18:09:44:57 INFO [evaluator:211] Setting random seed to 0 | Setting numpy seed to 1234 | Setting torch manual seed to 1234 | Setting fewshot manual seed to 1234
4
+ 2026-07-18:09:44:57 INFO [evaluator:236] Initializing hf model, with arguments: {'pretrained': '/workspace', 'trust_remote_code': True, 'dtype': 'bfloat16', 'max_length': 16384, 'attn_implementation': 'sdpa'}
5
+ 2026-07-18:09:45:00 INFO [models.huggingface:161] Using device 'cuda:0'
6
+ 2026-07-18:09:45:01 INFO [models.huggingface:423] Model parallel was set to False, max memory was not set, and device map was set to {'': 'cuda:0'}
7
+ Traceback (most recent call last):
8
+ File "<frozen runpy>", line 198, in _run_module_as_main
9
+ File "<frozen runpy>", line 88, in _run_code
10
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/__main__.py", line 14, in <module>
11
+ cli_evaluate()
12
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/__main__.py", line 10, in cli_evaluate
13
+ parser.execute(args)
14
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/_cli/harness.py", line 60, in execute
15
+ args.func(args)
16
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/_cli/run.py", line 379, in _execute
17
+ results = simple_evaluate(
18
+ ^^^^^^^^^^^^^^^^
19
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/utils.py", line 498, in _wrapper
20
+ return fn(*args, **kwargs)
21
+ ^^^^^^^^^^^^^^^^^^^
22
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/evaluator.py", line 239, in simple_evaluate
23
+ lm = lm_eval.api.registry.get_model(model).create_from_arg_obj(
24
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/api/model.py", line 180, in create_from_arg_obj
26
+ return cls(**arg_dict, **additional_config)
27
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/models/huggingface.py", line 224, in __init__
29
+ self._create_model(
30
+ File "/opt/conda/lib/python3.11/site-packages/lm_eval/models/huggingface.py", line 632, in _create_model
31
+ self._model = self.AUTO_MODEL_CLASS.from_pretrained(
32
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33
+ File "/opt/conda/lib/python3.11/site-packages/transformers/models/auto/auto_factory.py", line 600, in from_pretrained
34
+ return model_class.from_pretrained(
35
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36
+ File "/opt/conda/lib/python3.11/site-packages/transformers/modeling_utils.py", line 315, in _wrapper
37
+ return func(*args, **kwargs)
38
+ ^^^^^^^^^^^^^^^^^^^^^
39
+ File "/opt/conda/lib/python3.11/site-packages/transformers/modeling_utils.py", line 4927, in from_pretrained
40
+ model = cls(config, *model_args, **model_kwargs)
41
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
42
+ TypeError: Qwen3ForCausalLM.__init__() got an unexpected keyword argument 'dtype'
outputs/l2a_style/hotstart/added_tokens.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|box_end|>": 151649,
9
+ "<|box_start|>": 151648,
10
+ "<|endoftext|>": 151643,
11
+ "<|file_sep|>": 151664,
12
+ "<|fim_middle|>": 151660,
13
+ "<|fim_pad|>": 151662,
14
+ "<|fim_prefix|>": 151659,
15
+ "<|fim_suffix|>": 151661,
16
+ "<|im_end|>": 151645,
17
+ "<|im_start|>": 151644,
18
+ "<|image_pad|>": 151655,
19
+ "<|object_ref_end|>": 151647,
20
+ "<|object_ref_start|>": 151646,
21
+ "<|quad_end|>": 151651,
22
+ "<|quad_start|>": 151650,
23
+ "<|repo_name|>": 151663,
24
+ "<|video_pad|>": 151656,
25
+ "<|vision_end|>": 151653,
26
+ "<|vision_pad|>": 151654,
27
+ "<|vision_start|>": 151652
28
+ }
outputs/l2a_style/hotstart/aha_hotstart_manifest.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_checkpoint": "/workspace",
3
+ "router_granularity": "token",
4
+ "native_gate_rows_per_layer": 1,
5
+ "effective_gate_parameters": 57372,
6
+ "gate_init_full_probability": 0.9,
7
+ "gate_weight_init": "zeros",
8
+ "gate_bias_logit": 2.1972245773362196,
9
+ "local_attention": {
10
+ "kind": "sink_recent",
11
+ "sink_size": 64,
12
+ "recent_size": 256
13
+ }
14
+ }
outputs/l2a_style/hotstart/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 %}
outputs/l2a_style/hotstart/config.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_ce_weight": 1.0,
3
+ "aha_distill_weight": 0.0,
4
+ "aha_force_gate_value": null,
5
+ "aha_gate_init_full_prob": 0.9,
6
+ "aha_gate_target": 0.7,
7
+ "aha_hotstart_source": "/workspace",
8
+ "aha_lambda": 0.0003,
9
+ "aha_local_kind": "sink_recent",
10
+ "aha_mode": "dynamic",
11
+ "aha_reg_weight": -1.0,
12
+ "aha_router_granularity": "token",
13
+ "aha_window_size": 128,
14
+ "architectures": [
15
+ "AHAQwen3ForCausalLM"
16
+ ],
17
+ "attention_bias": true,
18
+ "attention_dropout": 0.0,
19
+ "auto_map": {
20
+ "AutoConfig": "modeling_aha_qwen3.AHAQwen3Config",
21
+ "AutoModelForCausalLM": "modeling_aha_qwen3.AHAQwen3ForCausalLM"
22
+ },
23
+ "bos_token_id": 151643,
24
+ "duo_alpha_init": 1.0,
25
+ "duo_recent_size": 256,
26
+ "duo_sink_size": 64,
27
+ "eos_token_id": 151645,
28
+ "head_dim": 128,
29
+ "hidden_act": "silu",
30
+ "hidden_size": 2048,
31
+ "initializer_range": 0.02,
32
+ "intermediate_size": 6144,
33
+ "layer_types": [
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
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention",
57
+ "full_attention",
58
+ "full_attention",
59
+ "full_attention",
60
+ "full_attention",
61
+ "full_attention"
62
+ ],
63
+ "max_position_embeddings": 40960,
64
+ "max_window_layers": 28,
65
+ "model_type": "aha_qwen3",
66
+ "num_attention_heads": 16,
67
+ "num_hidden_layers": 28,
68
+ "num_key_value_heads": 8,
69
+ "rms_norm_eps": 1e-06,
70
+ "rope_scaling": null,
71
+ "rope_theta": 1000000,
72
+ "sliding_window": null,
73
+ "tie_word_embeddings": false,
74
+ "torch_dtype": "float32",
75
+ "transformers_version": "4.54.0",
76
+ "use_cache": false,
77
+ "use_sliding_window": false,
78
+ "vocab_size": 151936
79
+ }
outputs/l2a_style/hotstart/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 151643,
4
+ "eos_token_id": 151645,
5
+ "transformers_version": "4.54.0",
6
+ "use_cache": false
7
+ }
outputs/l2a_style/hotstart/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/hotstart/model-00001-of-00002.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7ec09cb1fe723bc566f1cf9ee67dd3f3c70b13492d7479af18cba77dbc268f24
3
+ size 4970170548
outputs/l2a_style/hotstart/model-00002-of-00002.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3661376747fa164598c9dced9e5186d92d5e5789d90c138adecde7a8559f10e
3
+ size 3157754516
outputs/l2a_style/hotstart/model.safetensors.index.json ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metadata": {
3
+ "total_parameters": 2031969308,
4
+ "total_size": 8127877232
5
+ },
6
+ "weight_map": {
7
+ "lm_head.weight": "model-00002-of-00002.safetensors",
8
+ "model.embed_tokens.weight": "model-00001-of-00002.safetensors",
9
+ "model.layers.0.input_layernorm.weight": "model-00001-of-00002.safetensors",
10
+ "model.layers.0.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
11
+ "model.layers.0.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
12
+ "model.layers.0.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
13
+ "model.layers.0.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
14
+ "model.layers.0.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
15
+ "model.layers.0.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
16
+ "model.layers.0.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
17
+ "model.layers.0.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
18
+ "model.layers.0.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
19
+ "model.layers.0.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
20
+ "model.layers.0.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
21
+ "model.layers.0.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
22
+ "model.layers.0.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
23
+ "model.layers.0.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
24
+ "model.layers.1.input_layernorm.weight": "model-00001-of-00002.safetensors",
25
+ "model.layers.1.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
26
+ "model.layers.1.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
27
+ "model.layers.1.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
28
+ "model.layers.1.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
29
+ "model.layers.1.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
30
+ "model.layers.1.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
31
+ "model.layers.1.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
32
+ "model.layers.1.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
33
+ "model.layers.1.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
34
+ "model.layers.1.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
35
+ "model.layers.1.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
36
+ "model.layers.1.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
37
+ "model.layers.1.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
38
+ "model.layers.1.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
39
+ "model.layers.10.input_layernorm.weight": "model-00001-of-00002.safetensors",
40
+ "model.layers.10.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
41
+ "model.layers.10.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
42
+ "model.layers.10.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
43
+ "model.layers.10.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
44
+ "model.layers.10.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
45
+ "model.layers.10.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
46
+ "model.layers.10.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
47
+ "model.layers.10.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
48
+ "model.layers.10.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
49
+ "model.layers.10.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
50
+ "model.layers.10.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
51
+ "model.layers.10.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
52
+ "model.layers.10.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
53
+ "model.layers.10.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
54
+ "model.layers.11.input_layernorm.weight": "model-00001-of-00002.safetensors",
55
+ "model.layers.11.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
56
+ "model.layers.11.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
57
+ "model.layers.11.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
58
+ "model.layers.11.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
59
+ "model.layers.11.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
60
+ "model.layers.11.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
61
+ "model.layers.11.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
62
+ "model.layers.11.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
63
+ "model.layers.11.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
64
+ "model.layers.11.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
65
+ "model.layers.11.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
66
+ "model.layers.11.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
67
+ "model.layers.11.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
68
+ "model.layers.11.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
69
+ "model.layers.12.input_layernorm.weight": "model-00001-of-00002.safetensors",
70
+ "model.layers.12.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
71
+ "model.layers.12.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
72
+ "model.layers.12.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
73
+ "model.layers.12.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
74
+ "model.layers.12.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
75
+ "model.layers.12.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
76
+ "model.layers.12.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
77
+ "model.layers.12.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
78
+ "model.layers.12.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
79
+ "model.layers.12.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
80
+ "model.layers.12.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
81
+ "model.layers.12.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
82
+ "model.layers.12.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
83
+ "model.layers.12.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
84
+ "model.layers.13.input_layernorm.weight": "model-00001-of-00002.safetensors",
85
+ "model.layers.13.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
86
+ "model.layers.13.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
87
+ "model.layers.13.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
88
+ "model.layers.13.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
89
+ "model.layers.13.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
90
+ "model.layers.13.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
91
+ "model.layers.13.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
92
+ "model.layers.13.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
93
+ "model.layers.13.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
94
+ "model.layers.13.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
95
+ "model.layers.13.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
96
+ "model.layers.13.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
97
+ "model.layers.13.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
98
+ "model.layers.13.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
99
+ "model.layers.14.input_layernorm.weight": "model-00001-of-00002.safetensors",
100
+ "model.layers.14.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
101
+ "model.layers.14.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
102
+ "model.layers.14.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
103
+ "model.layers.14.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
104
+ "model.layers.14.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
105
+ "model.layers.14.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
106
+ "model.layers.14.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
107
+ "model.layers.14.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
108
+ "model.layers.14.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
109
+ "model.layers.14.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
110
+ "model.layers.14.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
111
+ "model.layers.14.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
112
+ "model.layers.14.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
113
+ "model.layers.14.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
114
+ "model.layers.15.input_layernorm.weight": "model-00001-of-00002.safetensors",
115
+ "model.layers.15.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
116
+ "model.layers.15.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
117
+ "model.layers.15.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
118
+ "model.layers.15.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
119
+ "model.layers.15.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
120
+ "model.layers.15.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
121
+ "model.layers.15.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
122
+ "model.layers.15.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
123
+ "model.layers.15.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
124
+ "model.layers.15.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
125
+ "model.layers.15.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
126
+ "model.layers.15.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
127
+ "model.layers.15.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
128
+ "model.layers.15.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
129
+ "model.layers.16.input_layernorm.weight": "model-00001-of-00002.safetensors",
130
+ "model.layers.16.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
131
+ "model.layers.16.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
132
+ "model.layers.16.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
133
+ "model.layers.16.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
134
+ "model.layers.16.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
135
+ "model.layers.16.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
136
+ "model.layers.16.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
137
+ "model.layers.16.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
138
+ "model.layers.16.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
139
+ "model.layers.16.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
140
+ "model.layers.16.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
141
+ "model.layers.16.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
142
+ "model.layers.16.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
143
+ "model.layers.16.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
144
+ "model.layers.17.input_layernorm.weight": "model-00001-of-00002.safetensors",
145
+ "model.layers.17.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
146
+ "model.layers.17.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
147
+ "model.layers.17.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
148
+ "model.layers.17.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
149
+ "model.layers.17.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
150
+ "model.layers.17.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
151
+ "model.layers.17.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
152
+ "model.layers.17.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
153
+ "model.layers.17.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
154
+ "model.layers.17.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
155
+ "model.layers.17.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
156
+ "model.layers.17.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
157
+ "model.layers.17.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
158
+ "model.layers.17.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
159
+ "model.layers.18.input_layernorm.weight": "model-00002-of-00002.safetensors",
160
+ "model.layers.18.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
161
+ "model.layers.18.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
162
+ "model.layers.18.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
163
+ "model.layers.18.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
164
+ "model.layers.18.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
165
+ "model.layers.18.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
166
+ "model.layers.18.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
167
+ "model.layers.18.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
168
+ "model.layers.18.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
169
+ "model.layers.18.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
170
+ "model.layers.18.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
171
+ "model.layers.18.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
172
+ "model.layers.18.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
173
+ "model.layers.18.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
174
+ "model.layers.19.input_layernorm.weight": "model-00002-of-00002.safetensors",
175
+ "model.layers.19.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
176
+ "model.layers.19.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
177
+ "model.layers.19.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
178
+ "model.layers.19.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
179
+ "model.layers.19.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
180
+ "model.layers.19.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
181
+ "model.layers.19.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
182
+ "model.layers.19.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
183
+ "model.layers.19.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
184
+ "model.layers.19.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
185
+ "model.layers.19.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
186
+ "model.layers.19.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
187
+ "model.layers.19.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
188
+ "model.layers.19.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
189
+ "model.layers.2.input_layernorm.weight": "model-00001-of-00002.safetensors",
190
+ "model.layers.2.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
191
+ "model.layers.2.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
192
+ "model.layers.2.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
193
+ "model.layers.2.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
194
+ "model.layers.2.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
195
+ "model.layers.2.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
196
+ "model.layers.2.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
197
+ "model.layers.2.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
198
+ "model.layers.2.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
199
+ "model.layers.2.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
200
+ "model.layers.2.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
201
+ "model.layers.2.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
202
+ "model.layers.2.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
203
+ "model.layers.2.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
204
+ "model.layers.20.input_layernorm.weight": "model-00002-of-00002.safetensors",
205
+ "model.layers.20.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
206
+ "model.layers.20.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
207
+ "model.layers.20.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
208
+ "model.layers.20.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
209
+ "model.layers.20.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
210
+ "model.layers.20.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
211
+ "model.layers.20.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
212
+ "model.layers.20.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
213
+ "model.layers.20.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
214
+ "model.layers.20.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
215
+ "model.layers.20.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
216
+ "model.layers.20.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
217
+ "model.layers.20.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
218
+ "model.layers.20.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
219
+ "model.layers.21.input_layernorm.weight": "model-00002-of-00002.safetensors",
220
+ "model.layers.21.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
221
+ "model.layers.21.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
222
+ "model.layers.21.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
223
+ "model.layers.21.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
224
+ "model.layers.21.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
225
+ "model.layers.21.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
226
+ "model.layers.21.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
227
+ "model.layers.21.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
228
+ "model.layers.21.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
229
+ "model.layers.21.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
230
+ "model.layers.21.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
231
+ "model.layers.21.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
232
+ "model.layers.21.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
233
+ "model.layers.21.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
234
+ "model.layers.22.input_layernorm.weight": "model-00002-of-00002.safetensors",
235
+ "model.layers.22.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
236
+ "model.layers.22.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
237
+ "model.layers.22.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
238
+ "model.layers.22.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
239
+ "model.layers.22.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
240
+ "model.layers.22.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
241
+ "model.layers.22.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
242
+ "model.layers.22.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
243
+ "model.layers.22.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
244
+ "model.layers.22.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
245
+ "model.layers.22.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
246
+ "model.layers.22.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
247
+ "model.layers.22.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
248
+ "model.layers.22.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
249
+ "model.layers.23.input_layernorm.weight": "model-00002-of-00002.safetensors",
250
+ "model.layers.23.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
251
+ "model.layers.23.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
252
+ "model.layers.23.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
253
+ "model.layers.23.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
254
+ "model.layers.23.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
255
+ "model.layers.23.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
256
+ "model.layers.23.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
257
+ "model.layers.23.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
258
+ "model.layers.23.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
259
+ "model.layers.23.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
260
+ "model.layers.23.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
261
+ "model.layers.23.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
262
+ "model.layers.23.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
263
+ "model.layers.23.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
264
+ "model.layers.24.input_layernorm.weight": "model-00002-of-00002.safetensors",
265
+ "model.layers.24.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
266
+ "model.layers.24.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
267
+ "model.layers.24.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
268
+ "model.layers.24.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
269
+ "model.layers.24.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
270
+ "model.layers.24.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
271
+ "model.layers.24.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
272
+ "model.layers.24.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
273
+ "model.layers.24.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
274
+ "model.layers.24.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
275
+ "model.layers.24.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
276
+ "model.layers.24.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
277
+ "model.layers.24.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
278
+ "model.layers.24.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
279
+ "model.layers.25.input_layernorm.weight": "model-00002-of-00002.safetensors",
280
+ "model.layers.25.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
281
+ "model.layers.25.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
282
+ "model.layers.25.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
283
+ "model.layers.25.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
284
+ "model.layers.25.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
285
+ "model.layers.25.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
286
+ "model.layers.25.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
287
+ "model.layers.25.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
288
+ "model.layers.25.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
289
+ "model.layers.25.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
290
+ "model.layers.25.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
291
+ "model.layers.25.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
292
+ "model.layers.25.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
293
+ "model.layers.25.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
294
+ "model.layers.26.input_layernorm.weight": "model-00002-of-00002.safetensors",
295
+ "model.layers.26.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
296
+ "model.layers.26.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
297
+ "model.layers.26.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
298
+ "model.layers.26.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
299
+ "model.layers.26.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
300
+ "model.layers.26.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
301
+ "model.layers.26.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
302
+ "model.layers.26.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
303
+ "model.layers.26.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
304
+ "model.layers.26.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
305
+ "model.layers.26.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
306
+ "model.layers.26.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
307
+ "model.layers.26.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
308
+ "model.layers.26.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
309
+ "model.layers.27.input_layernorm.weight": "model-00002-of-00002.safetensors",
310
+ "model.layers.27.mlp.down_proj.weight": "model-00002-of-00002.safetensors",
311
+ "model.layers.27.mlp.gate_proj.weight": "model-00002-of-00002.safetensors",
312
+ "model.layers.27.mlp.up_proj.weight": "model-00002-of-00002.safetensors",
313
+ "model.layers.27.post_attention_layernorm.weight": "model-00002-of-00002.safetensors",
314
+ "model.layers.27.self_attn.k_norm.weight": "model-00002-of-00002.safetensors",
315
+ "model.layers.27.self_attn.k_proj.bias": "model-00002-of-00002.safetensors",
316
+ "model.layers.27.self_attn.k_proj.weight": "model-00002-of-00002.safetensors",
317
+ "model.layers.27.self_attn.o_proj.bias": "model-00002-of-00002.safetensors",
318
+ "model.layers.27.self_attn.o_proj.weight": "model-00002-of-00002.safetensors",
319
+ "model.layers.27.self_attn.q_norm.weight": "model-00002-of-00002.safetensors",
320
+ "model.layers.27.self_attn.q_proj.bias": "model-00002-of-00002.safetensors",
321
+ "model.layers.27.self_attn.q_proj.weight": "model-00002-of-00002.safetensors",
322
+ "model.layers.27.self_attn.v_proj.bias": "model-00002-of-00002.safetensors",
323
+ "model.layers.27.self_attn.v_proj.weight": "model-00002-of-00002.safetensors",
324
+ "model.layers.3.input_layernorm.weight": "model-00001-of-00002.safetensors",
325
+ "model.layers.3.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
326
+ "model.layers.3.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
327
+ "model.layers.3.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
328
+ "model.layers.3.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
329
+ "model.layers.3.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
330
+ "model.layers.3.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
331
+ "model.layers.3.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
332
+ "model.layers.3.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
333
+ "model.layers.3.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
334
+ "model.layers.3.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
335
+ "model.layers.3.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
336
+ "model.layers.3.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
337
+ "model.layers.3.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
338
+ "model.layers.3.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
339
+ "model.layers.4.input_layernorm.weight": "model-00001-of-00002.safetensors",
340
+ "model.layers.4.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
341
+ "model.layers.4.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
342
+ "model.layers.4.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
343
+ "model.layers.4.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
344
+ "model.layers.4.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
345
+ "model.layers.4.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
346
+ "model.layers.4.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
347
+ "model.layers.4.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
348
+ "model.layers.4.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
349
+ "model.layers.4.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
350
+ "model.layers.4.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
351
+ "model.layers.4.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
352
+ "model.layers.4.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
353
+ "model.layers.4.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
354
+ "model.layers.5.input_layernorm.weight": "model-00001-of-00002.safetensors",
355
+ "model.layers.5.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
356
+ "model.layers.5.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
357
+ "model.layers.5.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
358
+ "model.layers.5.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
359
+ "model.layers.5.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
360
+ "model.layers.5.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
361
+ "model.layers.5.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
362
+ "model.layers.5.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
363
+ "model.layers.5.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
364
+ "model.layers.5.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
365
+ "model.layers.5.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
366
+ "model.layers.5.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
367
+ "model.layers.5.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
368
+ "model.layers.5.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
369
+ "model.layers.6.input_layernorm.weight": "model-00001-of-00002.safetensors",
370
+ "model.layers.6.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
371
+ "model.layers.6.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
372
+ "model.layers.6.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
373
+ "model.layers.6.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
374
+ "model.layers.6.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
375
+ "model.layers.6.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
376
+ "model.layers.6.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
377
+ "model.layers.6.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
378
+ "model.layers.6.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
379
+ "model.layers.6.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
380
+ "model.layers.6.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
381
+ "model.layers.6.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
382
+ "model.layers.6.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
383
+ "model.layers.6.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
384
+ "model.layers.7.input_layernorm.weight": "model-00001-of-00002.safetensors",
385
+ "model.layers.7.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
386
+ "model.layers.7.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
387
+ "model.layers.7.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
388
+ "model.layers.7.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
389
+ "model.layers.7.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
390
+ "model.layers.7.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
391
+ "model.layers.7.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
392
+ "model.layers.7.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
393
+ "model.layers.7.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
394
+ "model.layers.7.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
395
+ "model.layers.7.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
396
+ "model.layers.7.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
397
+ "model.layers.7.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
398
+ "model.layers.7.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
399
+ "model.layers.8.input_layernorm.weight": "model-00001-of-00002.safetensors",
400
+ "model.layers.8.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
401
+ "model.layers.8.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
402
+ "model.layers.8.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
403
+ "model.layers.8.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
404
+ "model.layers.8.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
405
+ "model.layers.8.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
406
+ "model.layers.8.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
407
+ "model.layers.8.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
408
+ "model.layers.8.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
409
+ "model.layers.8.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
410
+ "model.layers.8.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
411
+ "model.layers.8.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
412
+ "model.layers.8.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
413
+ "model.layers.8.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
414
+ "model.layers.9.input_layernorm.weight": "model-00001-of-00002.safetensors",
415
+ "model.layers.9.mlp.down_proj.weight": "model-00001-of-00002.safetensors",
416
+ "model.layers.9.mlp.gate_proj.weight": "model-00001-of-00002.safetensors",
417
+ "model.layers.9.mlp.up_proj.weight": "model-00001-of-00002.safetensors",
418
+ "model.layers.9.post_attention_layernorm.weight": "model-00001-of-00002.safetensors",
419
+ "model.layers.9.self_attn.k_norm.weight": "model-00001-of-00002.safetensors",
420
+ "model.layers.9.self_attn.k_proj.bias": "model-00001-of-00002.safetensors",
421
+ "model.layers.9.self_attn.k_proj.weight": "model-00001-of-00002.safetensors",
422
+ "model.layers.9.self_attn.o_proj.bias": "model-00001-of-00002.safetensors",
423
+ "model.layers.9.self_attn.o_proj.weight": "model-00001-of-00002.safetensors",
424
+ "model.layers.9.self_attn.q_norm.weight": "model-00001-of-00002.safetensors",
425
+ "model.layers.9.self_attn.q_proj.bias": "model-00001-of-00002.safetensors",
426
+ "model.layers.9.self_attn.q_proj.weight": "model-00001-of-00002.safetensors",
427
+ "model.layers.9.self_attn.v_proj.bias": "model-00001-of-00002.safetensors",
428
+ "model.layers.9.self_attn.v_proj.weight": "model-00001-of-00002.safetensors",
429
+ "model.norm.weight": "model-00002-of-00002.safetensors"
430
+ }
431
+ }
outputs/l2a_style/hotstart/modeling_aha_qwen3.py ADDED
@@ -0,0 +1,1226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AHA-Qwen3: All-or-Here Attention applied to Qwen3.
3
+
4
+ Minimal modification to standard Qwen3 attention: adds a binary gate that
5
+ dynamically toggles between full attention and local attention. The persisted
6
+ ``aha_router_granularity`` config selects either one gate per (token, KV-head)
7
+ or one gate per token shared by every head in the layer.
8
+
9
+ Based on: transformers.models.qwen3.modeling_qwen3 (Qwen3Attention)
10
+ Changes:
11
+ 1. q_proj outputs either one extra gate value (``token``) or
12
+ ``num_key_value_heads`` values (``token_kv_head``). The shared-token
13
+ value is broadcast to all KV/query heads before attention mixing.
14
+ 2. Gate: sigmoid → hard threshold → STE for training
15
+ 3. Double-mix: global attention + local window attention, blended by
16
+ gate (broadcast across the GQA group)
17
+ 4. Auxiliary loss: λ * mean(gate_soft) to encourage sparsity
18
+ """
19
+
20
+ import atexit
21
+ import json
22
+ import os
23
+ from dataclasses import dataclass
24
+ from pathlib import Path
25
+ from typing import Callable, Optional, Union
26
+
27
+ import torch
28
+ import torch.nn as nn
29
+ from safetensors.torch import load_file
30
+
31
+ from transformers import Qwen3Config, Qwen3ForCausalLM
32
+ from transformers.modeling_layers import GradientCheckpointingLayer
33
+ from transformers.models.qwen3.modeling_qwen3 import (
34
+ Qwen3Attention,
35
+ Qwen3DecoderLayer,
36
+ Qwen3Model,
37
+ Qwen3RMSNorm,
38
+ apply_rotary_pos_emb,
39
+ )
40
+ from transformers.cache_utils import Cache, DynamicCache
41
+ from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
42
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
43
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
44
+ from transformers.processing_utils import Unpack
45
+ from transformers.utils.generic import TransformersKwargs
46
+
47
+ # AHA constants
48
+ AHA_WINDOW_SIZE = 1024
49
+ AHA_LAMBDA = 3e-4
50
+ AHA_DISTILL_WEIGHT = 0.0
51
+ AHA_CE_WEIGHT = 1.0
52
+ # Gate-density ceiling (hinge target). The aux loss only fires when
53
+ # ``mean(gate_soft) > AHA_GATE_TARGET``, so sparsity cannot drop
54
+ # below ``1 - AHA_GATE_TARGET`` in steady state regardless of how
55
+ # large ``aha_lambda`` is. Setting this to 1.0 recovers the legacy
56
+ # behaviour (unconditional downward pressure).
57
+ AHA_GATE_TARGET = 1.0
58
+
59
+ # DuoAttention baseline knobs. When ``aha_mode == "duo"`` the per-(token,
60
+ # kv_head) gate is replaced by a per-(layer, kv_head) learnable scalar
61
+ # (``duo_alpha``, 224 scalars total for Qwen3-0.6B), and the "local"
62
+ # branch becomes a streaming mask with ``duo_sink_size`` attention sinks
63
+ # plus a recent window of ``duo_recent_size``. This is a strict reduction
64
+ # of the dynamic AHA gate and reproduces the DuoAttention paper's head
65
+ # classifier (Xiao et al. 2024) on the Qwen3 backbone.
66
+ AHA_MODE = "dynamic"
67
+ DUO_SINK_SIZE = 64
68
+ DUO_RECENT_SIZE = 256
69
+ DUO_ALPHA_INIT = 1.0
70
+ # Local-branch mask kind for the dual-branch attention (global vs local).
71
+ # - "sliding_window": last ``aha_window_size`` tokens, no attention sinks.
72
+ # Historical default for ``aha_mode="dynamic"`` (Bv3 was trained with this).
73
+ # - "sink_recent": first ``duo_sink_size`` tokens + last ``duo_recent_size``
74
+ # tokens, matching the DuoAttention paper. Historical hardcoded default for
75
+ # ``aha_mode="duo"`` (still forced for duo regardless of this field).
76
+ # § 9.4.13.3 found that hot-starting a dynamic-mode ckpt from a duo ckpt with
77
+ # sliding_window local loses ~20pp LongBench retention vs the duo source,
78
+ # almost entirely from passage_retrieval_en (sinks are critical for long
79
+ # passage retrieval). Setting this to "sink_recent" for hot-started dynamic
80
+ # ckpts closes that architectural gap.
81
+ AHA_LOCAL_KIND = "sliding_window"
82
+ AHA_ROUTER_GRANULARITY = "token_kv_head"
83
+
84
+
85
+ def aha_router_output_size(config: Qwen3Config) -> int:
86
+ """Return the number of native dynamic-router logits per token."""
87
+
88
+ granularity = getattr(config, "aha_router_granularity", AHA_ROUTER_GRANULARITY)
89
+ if granularity == "token":
90
+ return 1
91
+ if granularity == "token_kv_head":
92
+ return int(config.num_key_value_heads)
93
+ raise ValueError(f"Unsupported aha_router_granularity={granularity!r}")
94
+
95
+
96
+ class _AHAInferenceSparsityTracker:
97
+ """Accumulate exact inference hard-route counts without changing outputs."""
98
+
99
+ def __init__(self) -> None:
100
+ # Keep counters as device scalars until process exit. Calling .item()
101
+ # once per layer and decode token serialized CUDA and made the
102
+ # diagnostic tracker materially slow generation.
103
+ self.sparse_decisions: dict[int, torch.Tensor] = {}
104
+ self.total_decisions: dict[int, int] = {}
105
+ self.phase_sparse_decisions: dict[str, torch.Tensor] = {}
106
+ self.phase_total_decisions: dict[str, int] = {}
107
+ raw_thresholds = os.environ.get("AHA_COUNTERFACTUAL_THRESHOLDS", "")
108
+ self.counterfactual_thresholds = tuple(
109
+ sorted({float(value) for value in raw_thresholds.split(",") if value.strip()})
110
+ )
111
+ if any(value < 0.0 or value > 1.0 for value in self.counterfactual_thresholds):
112
+ raise ValueError("AHA_COUNTERFACTUAL_THRESHOLDS values must be in [0, 1]")
113
+ self.counterfactual_sparse: dict[float, torch.Tensor] = {}
114
+ self.router_granularity: Optional[str] = None
115
+ self.native_router_decisions = 0
116
+ atexit.register(self.write_stats)
117
+
118
+ def update(
119
+ self,
120
+ gate_hard: torch.Tensor,
121
+ gate_soft: torch.Tensor,
122
+ layer_idx: int,
123
+ phase: str,
124
+ router_granularity: str,
125
+ native_router_width: int,
126
+ ) -> None:
127
+ if self.router_granularity is None:
128
+ self.router_granularity = router_granularity
129
+ elif self.router_granularity != router_granularity:
130
+ raise RuntimeError(
131
+ "AHA sparsity tracker received mixed router granularities: "
132
+ f"{self.router_granularity!r} and {router_granularity!r}"
133
+ )
134
+ sparse = torch.count_nonzero(gate_hard == 0).to(torch.int64)
135
+ total = int(gate_hard.numel())
136
+ effective_width = int(gate_hard.shape[-1])
137
+ self.native_router_decisions += total // effective_width * native_router_width
138
+ if layer_idx not in self.sparse_decisions:
139
+ self.sparse_decisions[layer_idx] = sparse
140
+ else:
141
+ self.sparse_decisions[layer_idx].add_(sparse)
142
+ self.total_decisions[layer_idx] = self.total_decisions.get(layer_idx, 0) + total
143
+ if phase not in self.phase_sparse_decisions:
144
+ self.phase_sparse_decisions[phase] = sparse.clone()
145
+ else:
146
+ self.phase_sparse_decisions[phase].add_(sparse)
147
+ self.phase_total_decisions[phase] = self.phase_total_decisions.get(phase, 0) + total
148
+ if self.counterfactual_thresholds:
149
+ flat = gate_soft.detach().reshape(-1, 1)
150
+ thresholds = flat.new_tensor(self.counterfactual_thresholds).reshape(1, -1)
151
+ counts = (flat <= thresholds).sum(dim=0, dtype=torch.int64)
152
+ for threshold, count in zip(self.counterfactual_thresholds, counts.unbind()):
153
+ if threshold not in self.counterfactual_sparse:
154
+ self.counterfactual_sparse[threshold] = count
155
+ else:
156
+ self.counterfactual_sparse[threshold].add_(count)
157
+
158
+ def write_stats(self) -> None:
159
+ output = os.environ.get("AHA_SPARSITY_STATS_PATH")
160
+ if not output:
161
+ return
162
+ sparse_by_layer = {
163
+ layer_idx: int(value.item())
164
+ for layer_idx, value in self.sparse_decisions.items()
165
+ }
166
+ sparse = sum(sparse_by_layer.values())
167
+ total = sum(self.total_decisions.values())
168
+ per_layer = {}
169
+ for layer_idx in sorted(self.total_decisions):
170
+ layer_sparse = sparse_by_layer.get(layer_idx, 0)
171
+ layer_total = self.total_decisions[layer_idx]
172
+ per_layer[str(layer_idx)] = {
173
+ "sparse_decisions": layer_sparse,
174
+ "total_decisions": layer_total,
175
+ "sparsity": layer_sparse / layer_total if layer_total else None,
176
+ }
177
+ payload = {
178
+ "definition": (
179
+ "hard AHA gate zeros / all token x KV-head x layer decisions, "
180
+ "token-weighted over prefill and decode"
181
+ ),
182
+ "router_granularity": self.router_granularity,
183
+ "native_router_decisions": self.native_router_decisions,
184
+ "effective_router_decisions": total,
185
+ "native_router_note": (
186
+ "Native decisions count learned gate logits before broadcast; effective "
187
+ "decisions always use token x KV-head x layer for matched sparsity."
188
+ ),
189
+ "sparse_decisions": sparse,
190
+ "total_decisions": total,
191
+ "sparsity": sparse / total if total else None,
192
+ "full_attention_usage": 1.0 - sparse / total if total else None,
193
+ "per_layer": per_layer,
194
+ "by_phase": {
195
+ phase: {
196
+ "sparse_decisions": int(self.phase_sparse_decisions[phase].item()),
197
+ "total_decisions": phase_total,
198
+ "sparsity": int(self.phase_sparse_decisions[phase].item()) / phase_total
199
+ if phase_total else None,
200
+ "full_attention_usage": 1.0
201
+ - int(self.phase_sparse_decisions[phase].item()) / phase_total
202
+ if phase_total else None,
203
+ }
204
+ for phase, phase_total in sorted(self.phase_total_decisions.items())
205
+ },
206
+ }
207
+ if self.counterfactual_thresholds:
208
+ payload["counterfactual_sparsity_by_threshold"] = {
209
+ f"{threshold:.6g}": {
210
+ "sparse_decisions": int(self.counterfactual_sparse[threshold].item()),
211
+ "total_decisions": total,
212
+ "sparsity": int(self.counterfactual_sparse[threshold].item()) / total
213
+ if total else None,
214
+ }
215
+ for threshold in self.counterfactual_thresholds
216
+ }
217
+ payload["counterfactual_note"] = (
218
+ "Routing counts only; benchmark quality must be measured in a separate "
219
+ "run with AHA_GATE_HARD_THRESHOLD set to the selected threshold."
220
+ )
221
+ path = Path(output)
222
+ path.parent.mkdir(parents=True, exist_ok=True)
223
+ path.write_text(json.dumps(payload, indent=2) + "\n")
224
+
225
+
226
+ _AHA_INFERENCE_SPARSITY_TRACKER: Optional[_AHAInferenceSparsityTracker] = None
227
+
228
+
229
+ def _track_aha_inference_sparsity(
230
+ gate_hard: torch.Tensor,
231
+ gate_soft: torch.Tensor,
232
+ layer_idx: int,
233
+ phase: str,
234
+ router_granularity: str,
235
+ native_router_width: int,
236
+ ) -> None:
237
+ global _AHA_INFERENCE_SPARSITY_TRACKER
238
+ if not os.environ.get("AHA_SPARSITY_STATS_PATH"):
239
+ return
240
+ if _AHA_INFERENCE_SPARSITY_TRACKER is None:
241
+ _AHA_INFERENCE_SPARSITY_TRACKER = _AHAInferenceSparsityTracker()
242
+ _AHA_INFERENCE_SPARSITY_TRACKER.update(
243
+ gate_hard.detach(), gate_soft.detach(), layer_idx, phase,
244
+ router_granularity, native_router_width,
245
+ )
246
+
247
+
248
+ def _get_optional_nonnegative_int_env(name: str) -> Optional[int]:
249
+ raw = os.environ.get(name, "").strip()
250
+ if not raw:
251
+ return None
252
+ value = int(raw)
253
+ if value < 0:
254
+ raise ValueError(f"{name} must be non-negative, got {value}")
255
+ return value
256
+
257
+
258
+ def _apply_low_alpha_force_full_heads_from_env(model: nn.Module) -> None:
259
+ """Auto-protect the weakest static-full heads in duo_dynamic mode.
260
+
261
+ ``AHA_FORCE_LOW_ALPHA_FULL_HEADS=K`` selects the K smallest
262
+ ``full_attention_heads`` values among heads that static Duo still kept
263
+ full (alpha > 0.5). These are the fragile boundary full heads: raising the
264
+ dynamic threshold can close them, but static Duo's alpha says they should
265
+ not be fully discarded. The selected heads are OR-ed with any manually
266
+ supplied ``AHA_FORCE_FULL_HEADS`` mask.
267
+ """
268
+ k = _get_optional_nonnegative_int_env("AHA_FORCE_LOW_ALPHA_FULL_HEADS")
269
+ if not k:
270
+ return
271
+ if getattr(model.config, "aha_mode", "dynamic") != "duo_dynamic":
272
+ return
273
+
274
+ candidates: list[tuple[float, int, int]] = []
275
+ with torch.no_grad():
276
+ for layer_idx, layer in enumerate(model.model.layers):
277
+ attn = layer.self_attn
278
+ alpha = getattr(attn, "full_attention_heads", None)
279
+ if alpha is None:
280
+ continue
281
+ for head_idx, value in enumerate(alpha.detach().float().cpu().tolist()):
282
+ if value > 0.5:
283
+ candidates.append((float(value), layer_idx, head_idx))
284
+ selected = sorted(candidates)[: min(k, len(candidates))]
285
+ if not selected:
286
+ return
287
+
288
+ selected_by_layer: dict[int, list[int]] = {}
289
+ for _, layer_idx, head_idx in selected:
290
+ selected_by_layer.setdefault(layer_idx, []).append(head_idx)
291
+ with torch.no_grad():
292
+ for layer_idx, heads in selected_by_layer.items():
293
+ mask = model.model.layers[layer_idx].self_attn._aha_force_full_heads_mask
294
+ for head_idx in heads:
295
+ mask[head_idx] = True
296
+ model.config.aha_force_low_alpha_full_heads = int(k)
297
+ formatted = ",".join(f"{layer}:{head}" for _, layer, head in selected)
298
+ print(
299
+ f"[AHA] AHA_FORCE_LOW_ALPHA_FULL_HEADS={k} selected {len(selected)} "
300
+ f"duo_dynamic heads: {formatted}",
301
+ flush=True,
302
+ )
303
+
304
+
305
+ class AHAQwen3Config(Qwen3Config):
306
+ """Qwen3Config with AHA-specific parameters."""
307
+ model_type = "aha_qwen3"
308
+
309
+ def __init__(
310
+ self,
311
+ aha_window_size=AHA_WINDOW_SIZE,
312
+ aha_lambda=AHA_LAMBDA,
313
+ aha_distill_weight=AHA_DISTILL_WEIGHT,
314
+ aha_ce_weight=AHA_CE_WEIGHT,
315
+ aha_gate_target=AHA_GATE_TARGET,
316
+ aha_reg_weight=-1.0,
317
+ aha_force_gate_value=None,
318
+ aha_mode=AHA_MODE,
319
+ duo_sink_size=DUO_SINK_SIZE,
320
+ duo_recent_size=DUO_RECENT_SIZE,
321
+ duo_alpha_init=DUO_ALPHA_INIT,
322
+ aha_local_kind=AHA_LOCAL_KIND,
323
+ aha_router_granularity=AHA_ROUTER_GRANULARITY,
324
+ **kwargs,
325
+ ):
326
+ super().__init__(**kwargs)
327
+ self.aha_window_size = aha_window_size
328
+ self.aha_lambda = aha_lambda
329
+ # Per-layer attention distillation weight. When > 0 and model is
330
+ # training, each layer contributes ``mean(((1-g)·(global-local))**2)``
331
+ # to the total loss. This is a dense per-(token, head) signal that
332
+ # pushes the gate toward 1 where SWA is insufficient to approximate
333
+ # full attention. Cost: ~0 extra forward compute (global/local are
334
+ # already computed for the mix).
335
+ self.aha_distill_weight = aha_distill_weight
336
+ # Language-modeling CE weight for the training objective. Default 1.0
337
+ # keeps the standard SFT behaviour. Set to 0.0 to drop CE entirely
338
+ # and train the gate purely from ``aux + distill`` (useful when the
339
+ # backbone is frozen and we only want to shape the gate). Evaluation
340
+ # always reports unweighted ``ce_loss`` regardless of this setting.
341
+ self.aha_ce_weight = aha_ce_weight
342
+ # Direct Duo-style sparsity regularizer. When >= 0, this replaces the
343
+ # legacy hinge term and uses ``aha_reg_weight * mean(gate_soft)``.
344
+ # Default -1 keeps older checkpoints on their original hinge objective.
345
+ self.aha_reg_weight = float(aha_reg_weight)
346
+ # Optional override used by Duo-style dynamic distillation. When set
347
+ # to 1.0, dynamic mode behaves as a full-attention teacher for one
348
+ # forward; None keeps the learned gate.
349
+ self.aha_force_gate_value = aha_force_gate_value
350
+ # Target ceiling on mean(gate_soft). Aux loss is hinge-shaped:
351
+ # ``λ · max(0, mean(gate_soft) - τ)``. Setting τ < 1.0 guarantees
352
+ # the gate cannot drop below ``1 - τ`` local-attention fraction
353
+ # in steady state -- the failure mode we observed with the legacy
354
+ # unconditional aux, where ``λ = 1.0`` drove mean(gate_hard) to
355
+ # 0.005 (99.5% local) and killed long-context accuracy
356
+ # (gsm8k 74% -> 19%). Default 1.0 is a no-op for backward
357
+ # compatibility with checkpoints trained on the old loss.
358
+ self.aha_gate_target = aha_gate_target
359
+ # DuoAttention-style baseline. ``dynamic`` keeps the original per-
360
+ # (token, kv_head) MLP gate. ``duo`` replaces it with a per-(layer,
361
+ # kv_head) learnable scalar and swaps the local branch's mask for a
362
+ # streaming (sink + recent) pattern. ``duo_dynamic`` keeps the Duo
363
+ # static mask for KV-capacity planning, but applies a dynamic AHA gate
364
+ # only on the static-full heads: effective_full = duo_full AND dyn_gate.
365
+ # Sink / recent sizes are fixed at train time; set ``duo_sink_size=0``
366
+ # for the sink-ablation run.
367
+ if aha_mode not in ("dynamic", "duo", "duo_dynamic"):
368
+ raise ValueError(
369
+ f"aha_mode must be 'dynamic', 'duo', or 'duo_dynamic', got {aha_mode!r}"
370
+ )
371
+ self.aha_mode = aha_mode
372
+ if aha_router_granularity not in ("token", "token_kv_head"):
373
+ raise ValueError(
374
+ "aha_router_granularity must be 'token' or 'token_kv_head', "
375
+ f"got {aha_router_granularity!r}"
376
+ )
377
+ # Persist this even for static Duo checkpoints. Dynamic AHA uses it to
378
+ # choose the native q_proj gate-row count; old configs omit the field
379
+ # and therefore retain the historical token_kv_head behavior.
380
+ self.aha_router_granularity = aha_router_granularity
381
+ self.duo_sink_size = int(duo_sink_size)
382
+ self.duo_recent_size = int(duo_recent_size)
383
+ self.duo_alpha_init = float(duo_alpha_init)
384
+ if aha_local_kind not in ("sliding_window", "sink_recent"):
385
+ raise ValueError(
386
+ f"aha_local_kind must be 'sliding_window' or 'sink_recent', "
387
+ f"got {aha_local_kind!r}"
388
+ )
389
+ # When aha_mode="duo" the local branch is always sink+recent regardless
390
+ # of this field (forced to the historical DuoAttention behaviour). The
391
+ # field only controls the dynamic mode's local mask.
392
+ self.aha_local_kind = aha_local_kind
393
+
394
+
395
+ def _build_streaming_causal_mask(
396
+ seq_len_q: int,
397
+ seq_len_kv: int,
398
+ sink_size: int,
399
+ recent_size: int,
400
+ device: torch.device,
401
+ dtype: torch.dtype,
402
+ kv_offset: int = 0,
403
+ ) -> torch.Tensor:
404
+ """Construct a streaming attention mask (sink + recent, causal).
405
+
406
+ Returns an additive mask of shape ``[1, 1, Q, K]`` where
407
+ disallowed positions are ``finfo(dtype).min`` and allowed positions
408
+ are ``0``. A query at position ``q`` (absolute in the sequence,
409
+ with ``kv_offset`` tokens already in the KV cache) attends to a key
410
+ at position ``k`` iff ``k <= q_abs`` AND (``k < sink_size`` OR
411
+ ``q_abs - k < recent_size``).
412
+ """
413
+ q_abs = torch.arange(seq_len_q, device=device).unsqueeze(-1) + kv_offset # [Q, 1]
414
+ k_abs = torch.arange(seq_len_kv, device=device).unsqueeze(0) # [1, K]
415
+ causal = k_abs <= q_abs
416
+ sink = k_abs < sink_size if sink_size > 0 else torch.zeros_like(causal)
417
+ recent = (q_abs - k_abs) < recent_size
418
+ allowed = causal & (sink | recent)
419
+ mask = torch.zeros(seq_len_q, seq_len_kv, dtype=dtype, device=device)
420
+ mask.masked_fill_(~allowed, torch.finfo(dtype).min)
421
+ return mask.unsqueeze(0).unsqueeze(0)
422
+
423
+
424
+ class AHAQwen3Attention(nn.Module):
425
+ """Qwen3 attention with AHA gate for per-head local/global routing."""
426
+
427
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
428
+ super().__init__()
429
+ self.config = config
430
+ self.layer_idx = layer_idx
431
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
432
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
433
+ self.scaling = self.head_dim ** -0.5
434
+ self.attention_dropout = config.attention_dropout
435
+ self.is_causal = True
436
+ self.aha_window_size = config.aha_window_size
437
+
438
+ # q_proj: extra router outputs for either one shared per-token gate or
439
+ # one per-(token, KV-head) gate.
440
+ # DUO mode uses a per-(layer, kv_head) static scalar instead, so
441
+ # q_proj keeps its vanilla Qwen3 shape and is binary-compatible
442
+ # with the base model checkpoint.
443
+ self._has_dyn_gate_logits = getattr(config, "aha_mode", "dynamic") in (
444
+ "dynamic",
445
+ "duo_dynamic",
446
+ )
447
+ self.aha_router_outputs = aha_router_output_size(config)
448
+ q_out_dim = config.num_attention_heads * self.head_dim
449
+ if self._has_dyn_gate_logits:
450
+ q_out_dim += self.aha_router_outputs
451
+ self.q_proj = nn.Linear(
452
+ config.hidden_size,
453
+ q_out_dim,
454
+ bias=config.attention_bias,
455
+ )
456
+ self.k_proj = nn.Linear(
457
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
458
+ )
459
+ self.v_proj = nn.Linear(
460
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
461
+ )
462
+ self.o_proj = nn.Linear(
463
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
464
+ )
465
+ # Qwen3 applies q_norm/k_norm per head_dim (not full projection)
466
+ self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
467
+ self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
468
+
469
+ # DuoAttention baseline: per-(layer, kv_head) learnable scalar.
470
+ # Populated in ``duo`` and ``duo_dynamic``. In ``duo_dynamic`` this
471
+ # scalar is the frozen Duo capacity mask; the dynamic gate is only
472
+ # allowed to close heads where this scalar says "full". Registered
473
+ # as ``full_attention_heads`` to match the name used in the
474
+ # official DuoAttention repo (``duo_attn.patch.llama``), so
475
+ # per-head statistics (e.g. retrieval heads vs streaming heads)
476
+ # can be read back with the same tooling.
477
+ if getattr(config, "aha_mode", "dynamic") in ("duo", "duo_dynamic"):
478
+ init = float(getattr(config, "duo_alpha_init", 1.0))
479
+ self.full_attention_heads = nn.Parameter(
480
+ torch.full((config.num_key_value_heads,), init, dtype=torch.float32)
481
+ )
482
+ else:
483
+ self.full_attention_heads = None
484
+ force_full_mask = torch.zeros(config.num_key_value_heads, dtype=torch.bool)
485
+ for raw in os.environ.get("AHA_FORCE_FULL_HEADS", "").split(","):
486
+ raw = raw.strip()
487
+ if not raw:
488
+ continue
489
+ layer_s, head_s = raw.split(":", 1)
490
+ if int(layer_s) == int(layer_idx):
491
+ force_full_mask[int(head_s)] = True
492
+ self.register_buffer(
493
+ "_aha_force_full_heads_mask",
494
+ force_full_mask,
495
+ persistent=False,
496
+ )
497
+
498
+ def forward(
499
+ self,
500
+ hidden_states: torch.Tensor,
501
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
502
+ attention_mask: Optional[dict], # dict with "global" and "local" keys
503
+ past_key_values: Optional[Cache] = None,
504
+ cache_position: Optional[torch.LongTensor] = None,
505
+ **kwargs,
506
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
507
+ input_shape = hidden_states.shape[:-1]
508
+ hidden_shape = (*input_shape, -1, self.head_dim)
509
+
510
+ # === Q projection + (optional) gate extraction ===
511
+ q_proj_out = self.q_proj(hidden_states)
512
+ if self._has_dyn_gate_logits:
513
+ query_states, gate = torch.split(
514
+ q_proj_out,
515
+ [
516
+ self.config.num_attention_heads * self.head_dim,
517
+ self.aha_router_outputs,
518
+ ],
519
+ dim=-1,
520
+ )
521
+ else:
522
+ query_states = q_proj_out
523
+ # DUO mode: use a zero placeholder so downstream dtype/device
524
+ # checks stay unchanged. The actual gate comes from the
525
+ # per-kv-head scalar ``full_attention_heads`` below.
526
+ gate = torch.zeros(
527
+ *input_shape, self.config.num_key_value_heads,
528
+ dtype=query_states.dtype, device=query_states.device,
529
+ )
530
+
531
+ # Qwen3-style: norm then reshape then transpose
532
+ query_states = self.q_norm(query_states.view(hidden_shape)).transpose(1, 2)
533
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
534
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
535
+
536
+ cos, sin = position_embeddings
537
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
538
+
539
+ if past_key_values is not None:
540
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
541
+ key_states, value_states = past_key_values.update(
542
+ key_states, value_states, self.layer_idx, cache_kwargs
543
+ )
544
+
545
+ # === Gate computation ===
546
+ # gate shape: (B, Q, H_kv) — per-(token, kv_head) in dynamic mode,
547
+ # or per-kv_head broadcast to (B, Q, H_kv) in DUO mode.
548
+ # At inference, threshold is read from env var AHA_GATE_HARD_THRESHOLD
549
+ # so users can trade sparsity for quality without re-training.
550
+ # Default 0.5 keeps training-time behaviour. Lowering to e.g. 0.35
551
+ # lets more (token, kv_head) pairs route through global attention,
552
+ # which recovers long-CoT quality at the cost of sparsity.
553
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
554
+ if aha_mode == "duo":
555
+ # DuoAttention: alpha is per-(layer, kv_head) learnable scalar.
556
+ # gate_soft = clamp(alpha, 0, 1); gate_hard = (alpha > 0.5).
557
+ # Both broadcast to (B, Q, H_kv) for a uniform downstream API
558
+ # (so the distill / aux / blend paths do not need to branch).
559
+ qhead_alpha = getattr(self, "_aha_qhead_full_attention_heads", None)
560
+ if qhead_alpha is not None:
561
+ # Eval-only oracle diagnostic: route at Q-head granularity
562
+ # instead of KV-head/GQA-group granularity. This does not
563
+ # represent a KV-capacity-saving deployment point; it isolates
564
+ # whether GQA grouping itself is the quality bottleneck.
565
+ alpha = qhead_alpha.to(gate.dtype).clamp(0.0, 1.0)
566
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
567
+ import os as _os
568
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
569
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
570
+ gate_ste = gate_hard
571
+
572
+ else:
573
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
574
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
575
+ if self.training:
576
+ gate_hard = (alpha > 0.5).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
577
+ # Straight-through estimator through the clamped alpha so
578
+ # L1 / distill gradients flow into the scalar.
579
+ gate_ste = gate_soft
580
+ else:
581
+ import os as _os
582
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
583
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
584
+ gate_ste = gate_hard
585
+ elif aha_mode == "duo_dynamic":
586
+ # Duo + AHA-on-full-heads:
587
+ # duo_full_mask = 1 for static Duo full/retrieval heads
588
+ # dyn_gate = per-token AHA decision inside those heads
589
+ # effective_full = duo_full_mask AND dyn_gate
590
+ #
591
+ # Duo streaming heads are locked to local/streaming forever. This
592
+ # preserves Duo's KV-capacity saving: only static-full heads need a
593
+ # full KV cache. ``aha_force_gate_value=1`` therefore reproduces
594
+ # the static Duo deployment point exactly, not all-full attention.
595
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
596
+ duo_full = (alpha > 0.5).to(gate.dtype).view(1, 1, -1)
597
+ dyn_soft = torch.sigmoid(gate)
598
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
599
+ if force_gate_value is not None:
600
+ dyn_soft = torch.full_like(dyn_soft, float(force_gate_value))
601
+ gate_soft = dyn_soft * duo_full
602
+ gate_hard = gate_soft
603
+ gate_ste = gate_soft
604
+ elif self.training:
605
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
606
+ gate_soft = dyn_soft * duo_full
607
+ dyn_hard = (dyn_soft > train_thr).to(gate.dtype)
608
+ gate_hard = dyn_hard * duo_full
609
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
610
+ else:
611
+ import os as _os
612
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
613
+ gate_soft = dyn_soft * duo_full
614
+ dyn_hard = (dyn_soft > _thr).to(gate.dtype)
615
+ gate_hard = dyn_hard * duo_full
616
+ gate_ste = gate_hard
617
+ force_full = self._aha_force_full_heads_mask.to(gate.dtype).view(1, 1, -1)
618
+ if bool(force_full.any().item()):
619
+ force_full = force_full * duo_full
620
+ gate_hard = torch.maximum(gate_hard, force_full)
621
+ gate_ste = torch.maximum(gate_ste, force_full)
622
+ else:
623
+ gate_soft = torch.sigmoid(gate)
624
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
625
+ if force_gate_value is not None:
626
+ force = float(force_gate_value)
627
+ gate_soft = torch.full_like(gate_soft, force)
628
+ gate_hard = torch.full_like(gate_soft, force)
629
+ gate_ste = gate_soft
630
+ elif self.training:
631
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
632
+ gate_hard = (gate_soft > train_thr).to(gate.dtype)
633
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
634
+ else:
635
+ import os as _os
636
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
637
+ gate_hard = (gate_soft > _thr).to(gate.dtype)
638
+ gate_ste = gate_hard
639
+
640
+ # Normalize the downstream contract to effective
641
+ # token x KV-head decisions. In shared-token mode there is only one
642
+ # learned logit per token, but regularization, sparsity accounting, and
643
+ # attention mixing all see the same KV-head denominator as the
644
+ # head-granular arm.
645
+ if gate_soft.shape[-1] == 1 and self.config.num_key_value_heads != 1:
646
+ effective_shape = (*gate_soft.shape[:-1], self.config.num_key_value_heads)
647
+ gate_soft = gate_soft.expand(effective_shape)
648
+ gate_hard = gate_hard.expand(effective_shape)
649
+ gate_ste = gate_ste.expand(effective_shape)
650
+
651
+ if (
652
+ aha_mode in ("duo", "duo_dynamic")
653
+ and not self.training
654
+ and os.environ.get("AHA_DUO_PREFILL_FULL", "0") == "1"
655
+ and query_states.shape[-2] == key_states.shape[-2]
656
+ ):
657
+ # Diagnostic parity with the official DuoAttention HF eval path:
658
+ # initial prefill is full attention; streaming heads are only
659
+ # localized during subsequent decode steps.
660
+ gate_hard = torch.ones_like(gate_hard)
661
+ gate_ste = torch.ones_like(gate_ste)
662
+
663
+ if not self.training:
664
+ phase = (
665
+ "decode"
666
+ if hidden_states.shape[1] == 1 and key_states.shape[-2] > 1
667
+ else "prefill"
668
+ )
669
+ _track_aha_inference_sparsity(
670
+ gate_hard, gate_soft, self.layer_idx, phase,
671
+ getattr(
672
+ self.config,
673
+ "aha_router_granularity",
674
+ AHA_ROUTER_GRANULARITY,
675
+ ),
676
+ self.aha_router_outputs if self._has_dyn_gate_logits
677
+ else self.config.num_key_value_heads,
678
+ )
679
+
680
+ # === Attention: double_mix (global + local, blended by gate) ===
681
+ from transformers.models.qwen3.modeling_qwen3 import eager_attention_forward
682
+
683
+ attention_interface: Callable = eager_attention_forward
684
+ if self.config._attn_implementation != "eager":
685
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
686
+
687
+ # Global attention (full context)
688
+ global_attn_output, _ = attention_interface(
689
+ self, query_states, key_states, value_states,
690
+ attention_mask["global"],
691
+ dropout=0.0 if not self.training else self.attention_dropout,
692
+ scaling=self.scaling,
693
+ **kwargs,
694
+ )
695
+
696
+ if getattr(self.config, "_aha_teacher_full_fastpath", False):
697
+ attn_output = global_attn_output.reshape(*input_shape, -1).contiguous()
698
+ attn_output = self.o_proj(attn_output)
699
+ return attn_output, gate_soft, gate_hard, None
700
+
701
+ # Local / streaming branch. The local mask was pre-built by the
702
+ # model's ``forward`` either as sliding-window (legacy dynamic mode
703
+ # default) or sink+recent (duo mode, and dynamic mode when
704
+ # ``aha_local_kind="sink_recent"``). Only pass ``sliding_window`` to
705
+ # the SDPA backend when the mask is the sliding-window kind, otherwise
706
+ # the backend will double-mask and wipe out the sink positions.
707
+ local_attn_kwargs = dict(
708
+ dropout=0.0 if not self.training else self.attention_dropout,
709
+ scaling=self.scaling,
710
+ )
711
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
712
+ self.config, "aha_local_kind", "sliding_window"
713
+ )
714
+ if local_kind == "sliding_window":
715
+ local_attn_kwargs["sliding_window"] = self.aha_window_size
716
+ local_attn_output, _ = attention_interface(
717
+ self, query_states, key_states, value_states,
718
+ attention_mask["local"],
719
+ **local_attn_kwargs,
720
+ **kwargs,
721
+ )
722
+
723
+ # Blend: normal gate_ste is (B, Q, H_kv); broadcast to (B, Q, H, 1)
724
+ # by repeating each kv_head decision. The optional qhead oracle above
725
+ # already emits (B, Q, H), so it bypasses this broadcast.
726
+ if gate_ste.shape[-1] == self.config.num_attention_heads:
727
+ g_bqh = gate_ste
728
+ else:
729
+ g_bqh = gate_ste.repeat_interleave(self.num_key_value_groups, dim=-1)
730
+ attn_output = (
731
+ global_attn_output * g_bqh.unsqueeze(-1)
732
+ + local_attn_output * (1 - g_bqh.unsqueeze(-1))
733
+ )
734
+
735
+ # Per-layer attention distillation signal. Returns a scalar so the
736
+ # outer model can aggregate across layers. When gate → 1 the term
737
+ # vanishes; when gate → 0 on positions where global/local disagree
738
+ # strongly, the gradient pushes gate back toward 1.
739
+ #
740
+ # ``diff`` is ``detach``-ed so distill only trains the gate; it does
741
+ # not reshape attention outputs through the backbone. This also
742
+ # prevents the squared-difference graph from retaining activations
743
+ # for all 28 layers' global/local branches, which was the source of
744
+ # OOM when gradient checkpointing is disabled on the custom forward.
745
+ distill_per_layer: Optional[torch.Tensor] = None
746
+ if self.training and getattr(self.config, "aha_distill_weight", 0.0) > 0.0:
747
+ with torch.no_grad():
748
+ diff_sq = (global_attn_output - local_attn_output).float() ** 2
749
+ one_minus_g_sq = ((1.0 - g_bqh.float()) ** 2).unsqueeze(-1)
750
+ distill_per_layer = (one_minus_g_sq * diff_sq).mean()
751
+
752
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
753
+ attn_output = self.o_proj(attn_output)
754
+ return attn_output, gate_soft, gate_hard, distill_per_layer
755
+
756
+
757
+ class AHAQwen3DecoderLayer(GradientCheckpointingLayer):
758
+ """Decoder layer with gradient-checkpointing support.
759
+
760
+ Inheriting from ``GradientCheckpointingLayer`` lets HuggingFace Trainer's
761
+ ``gradient_checkpointing_enable`` take effect on this layer without any
762
+ explicit ``_gradient_checkpointing_func`` call in the outer forward.
763
+ """
764
+
765
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
766
+ super().__init__()
767
+ self.hidden_size = config.hidden_size
768
+ self.self_attn = AHAQwen3Attention(config=config, layer_idx=layer_idx)
769
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3MLP
770
+ self.mlp = Qwen3MLP(config)
771
+ self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
772
+ self.post_attention_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
773
+
774
+ def forward(
775
+ self,
776
+ hidden_states: torch.Tensor,
777
+ attention_mask: Optional[dict] = None,
778
+ position_ids: Optional[torch.LongTensor] = None,
779
+ past_key_values: Optional[Cache] = None,
780
+ use_cache: Optional[bool] = False,
781
+ cache_position: Optional[torch.LongTensor] = None,
782
+ position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
783
+ **kwargs,
784
+ ):
785
+ residual = hidden_states
786
+ hidden_states = self.input_layernorm(hidden_states)
787
+ hidden_states, gate_soft, gate_hard, distill_per_layer = self.self_attn(
788
+ hidden_states=hidden_states,
789
+ attention_mask=attention_mask,
790
+ position_ids=position_ids,
791
+ past_key_values=past_key_values,
792
+ use_cache=use_cache,
793
+ cache_position=cache_position,
794
+ position_embeddings=position_embeddings,
795
+ **kwargs,
796
+ )
797
+ hidden_states = residual + hidden_states
798
+
799
+ residual = hidden_states
800
+ hidden_states = self.post_attention_layernorm(hidden_states)
801
+ hidden_states = self.mlp(hidden_states)
802
+ hidden_states = residual + hidden_states
803
+ return hidden_states, gate_soft, gate_hard, distill_per_layer
804
+
805
+
806
+ @dataclass
807
+ class AHACausalLMOutputWithPast(CausalLMOutputWithPast):
808
+ """Causal LM output with AHA loss breakdown and gate density stats (training / eval with labels)."""
809
+
810
+ ce_loss: Optional[torch.FloatTensor] = None
811
+ gate_aux_loss: Optional[torch.FloatTensor] = None
812
+ distill_loss: Optional[torch.FloatTensor] = None
813
+ gate_soft_mean: Optional[torch.FloatTensor] = None
814
+ gate_hard_mean: Optional[torch.FloatTensor] = None
815
+
816
+
817
+ class AHAModelOutputWithPast(BaseModelOutputWithPast):
818
+ def __init__(self, last_hidden_state, past_key_values=None, hidden_states=None, attentions=None,
819
+ all_gate_soft=None, all_gate_hard=None, all_distill_per_layer=None):
820
+ super().__init__(last_hidden_state=last_hidden_state, past_key_values=past_key_values,
821
+ hidden_states=hidden_states, attentions=attentions)
822
+ self.all_gate_soft = all_gate_soft
823
+ self.all_gate_hard = all_gate_hard
824
+ # Tuple of per-layer distill scalars, or empty tuple when distillation is off.
825
+ self.all_distill_per_layer = all_distill_per_layer if all_distill_per_layer is not None else ()
826
+
827
+
828
+ class AHAQwen3Model(Qwen3Model):
829
+ config_class = AHAQwen3Config
830
+
831
+ def __init__(self, config: AHAQwen3Config):
832
+ from transformers import PreTrainedModel
833
+ PreTrainedModel.__init__(self, config)
834
+ self.padding_idx = config.pad_token_id
835
+ self.vocab_size = config.vocab_size
836
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
837
+ self.layers = nn.ModuleList(
838
+ [AHAQwen3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
839
+ )
840
+ self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
841
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3RotaryEmbedding
842
+ self.rotary_emb = Qwen3RotaryEmbedding(config=config)
843
+ self.gradient_checkpointing = False
844
+ self.post_init()
845
+
846
+ def forward(
847
+ self,
848
+ input_ids: Optional[torch.LongTensor] = None,
849
+ attention_mask: Optional[torch.Tensor] = None,
850
+ position_ids: Optional[torch.LongTensor] = None,
851
+ past_key_values: Optional[Cache] = None,
852
+ inputs_embeds: Optional[torch.FloatTensor] = None,
853
+ use_cache: Optional[bool] = None,
854
+ cache_position: Optional[torch.LongTensor] = None,
855
+ **kwargs,
856
+ ) -> AHAModelOutputWithPast:
857
+ if (input_ids is None) ^ (inputs_embeds is not None):
858
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
859
+ if inputs_embeds is None:
860
+ inputs_embeds = self.embed_tokens(input_ids)
861
+ if use_cache and past_key_values is None:
862
+ past_key_values = DynamicCache(config=self.config)
863
+ if cache_position is None:
864
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
865
+ cache_position = torch.arange(
866
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
867
+ )
868
+ if position_ids is None:
869
+ position_ids = cache_position.unsqueeze(0)
870
+
871
+ # Build two masks: global (full causal) and local (sliding window
872
+ # for dynamic mode, streaming sink+recent for DUO mode).
873
+ orig_sliding_window = getattr(self.config, "sliding_window", None)
874
+ self.config.sliding_window = self.config.aha_window_size
875
+ mask_kwargs = {
876
+ "config": self.config,
877
+ "input_embeds": inputs_embeds,
878
+ "attention_mask": attention_mask,
879
+ "cache_position": cache_position,
880
+ "past_key_values": past_key_values,
881
+ "position_ids": position_ids,
882
+ }
883
+ global_mask = create_causal_mask(**mask_kwargs)
884
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
885
+ # duo / duo_dynamic force sink_recent for backward compat. dynamic mode
886
+ # reads the local kind from config (default sliding_window for legacy
887
+ # Bv3 ckpts; hot-started ckpts from duo set sink_recent — see
888
+ # §9.4.13.3).
889
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
890
+ self.config, "aha_local_kind", "sliding_window"
891
+ )
892
+ if local_kind == "sink_recent":
893
+ seq_len_q = inputs_embeds.shape[1]
894
+ past_seen = past_key_values.get_seq_length() if past_key_values is not None else 0
895
+ seq_len_kv = seq_len_q + past_seen
896
+ local_mask = _build_streaming_causal_mask(
897
+ seq_len_q=seq_len_q,
898
+ seq_len_kv=seq_len_kv,
899
+ sink_size=int(getattr(self.config, "duo_sink_size", 64)),
900
+ recent_size=int(getattr(self.config, "duo_recent_size", 256)),
901
+ device=inputs_embeds.device,
902
+ dtype=inputs_embeds.dtype,
903
+ kv_offset=past_seen,
904
+ )
905
+ else:
906
+ local_mask = create_sliding_window_causal_mask(**mask_kwargs)
907
+ causal_mask_dict = {"global": global_mask, "local": local_mask}
908
+ self.config.sliding_window = orig_sliding_window
909
+
910
+ hidden_states = inputs_embeds
911
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
912
+
913
+ all_gate_soft = ()
914
+ all_gate_hard = ()
915
+ all_distill_per_layer: tuple[torch.Tensor, ...] = ()
916
+ for decoder_layer in self.layers:
917
+ hidden_states, gate_soft, gate_hard, distill_per_layer = decoder_layer(
918
+ hidden_states,
919
+ attention_mask=causal_mask_dict,
920
+ position_ids=position_ids,
921
+ past_key_values=past_key_values,
922
+ use_cache=use_cache,
923
+ cache_position=cache_position,
924
+ position_embeddings=position_embeddings,
925
+ **kwargs,
926
+ )
927
+ all_gate_soft += (gate_soft,)
928
+ all_gate_hard += (gate_hard,)
929
+ if distill_per_layer is not None:
930
+ all_distill_per_layer += (distill_per_layer,)
931
+
932
+ hidden_states = self.norm(hidden_states)
933
+ return AHAModelOutputWithPast(
934
+ last_hidden_state=hidden_states,
935
+ past_key_values=past_key_values if use_cache else None,
936
+ all_gate_soft=all_gate_soft,
937
+ all_gate_hard=all_gate_hard,
938
+ all_distill_per_layer=all_distill_per_layer,
939
+ )
940
+
941
+
942
+ class AHAQwen3ForCausalLM(Qwen3ForCausalLM):
943
+ config_class = AHAQwen3Config
944
+ _tied_weights_keys = ["lm_head.weight"]
945
+
946
+ def __init__(self, config: AHAQwen3Config):
947
+ # Skip Qwen3ForCausalLM.__init__ which creates Qwen3Model
948
+ # Go to PreTrainedModel.__init__ directly
949
+ from transformers import PreTrainedModel
950
+ PreTrainedModel.__init__(self, config)
951
+ self.model = AHAQwen3Model(config)
952
+ self.vocab_size = config.vocab_size
953
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
954
+ self.post_init()
955
+
956
+ def forward(
957
+ self,
958
+ input_ids: Optional[torch.LongTensor] = None,
959
+ attention_mask: Optional[torch.Tensor] = None,
960
+ position_ids: Optional[torch.LongTensor] = None,
961
+ past_key_values: Optional[Cache] = None,
962
+ inputs_embeds: Optional[torch.FloatTensor] = None,
963
+ labels: Optional[torch.LongTensor] = None,
964
+ use_cache: Optional[bool] = None,
965
+ cache_position: Optional[torch.LongTensor] = None,
966
+ logits_to_keep: Union[int, torch.Tensor] = 0,
967
+ **kwargs,
968
+ ) -> AHACausalLMOutputWithPast:
969
+ outputs = self.model(
970
+ input_ids=input_ids,
971
+ attention_mask=attention_mask,
972
+ position_ids=position_ids,
973
+ past_key_values=past_key_values,
974
+ inputs_embeds=inputs_embeds,
975
+ use_cache=use_cache,
976
+ cache_position=cache_position,
977
+ **kwargs,
978
+ )
979
+ hidden_states = outputs.last_hidden_state
980
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
981
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
982
+
983
+ loss = None
984
+ ce_loss = None
985
+ gate_aux_loss = None
986
+ distill_loss = None
987
+ gate_soft_mean = None
988
+ gate_hard_mean = None
989
+
990
+ if labels is not None:
991
+ from torch.nn import CrossEntropyLoss
992
+ shift_logits = logits[..., :-1, :].contiguous()
993
+ shift_labels = labels[..., 1:].contiguous()
994
+ loss_fct = CrossEntropyLoss()
995
+ ce_loss = loss_fct(shift_logits.view(-1, self.vocab_size), shift_labels.view(-1))
996
+
997
+ all_gate_soft = outputs.all_gate_soft
998
+ all_gate_hard = outputs.all_gate_hard
999
+ if labels is not None and len(all_gate_soft) > 0 and len(all_gate_hard) > 0:
1000
+ gate_soft_stack = torch.stack(
1001
+ [g.to(hidden_states.device) for g in all_gate_soft], dim=-1
1002
+ ).float()
1003
+ gate_hard_stack = torch.stack(
1004
+ [g.to(hidden_states.device).float() for g in all_gate_hard], dim=-1
1005
+ )
1006
+ shift_gate_soft = gate_soft_stack[:, :-1, :].contiguous()
1007
+ shift_gate_hard = gate_hard_stack[:, :-1, :].contiguous()
1008
+ # Match the CE/PPL population exactly. The old unmasked mean
1009
+ # counted padded sequence positions as real gate decisions, which
1010
+ # made the training sparsity curve disagree with inference-time
1011
+ # sparsity on variable-length batches.
1012
+ valid_gate = shift_labels.ne(-100)
1013
+ if attention_mask is not None:
1014
+ valid_gate = valid_gate & attention_mask[:, 1:].to(torch.bool)
1015
+ if bool(valid_gate.any()):
1016
+ valid_gate = valid_gate[..., None, None].to(shift_gate_soft.dtype)
1017
+ normalizer = (
1018
+ valid_gate.sum()
1019
+ * shift_gate_soft.shape[-2]
1020
+ * shift_gate_soft.shape[-1]
1021
+ )
1022
+ gate_soft_mean = (shift_gate_soft * valid_gate).sum() / normalizer
1023
+ gate_hard_mean = (shift_gate_hard * valid_gate).sum() / normalizer
1024
+ else:
1025
+ gate_soft_mean = shift_gate_soft.mean()
1026
+ gate_hard_mean = shift_gate_hard.mean()
1027
+
1028
+ # Per-layer distillation loss: mean over layers of
1029
+ # ``mean(((1-g)·(global-local))**2)``. Populated only when training
1030
+ # with ``config.aha_distill_weight > 0`` (see AHAQwen3Attention).
1031
+ all_distill = outputs.all_distill_per_layer
1032
+ if self.training and len(all_distill) > 0:
1033
+ distill_loss = torch.stack(list(all_distill)).mean()
1034
+
1035
+ # === Loss assembly ===
1036
+ # Eval path: standard LM loss for compatibility with HF metrics.
1037
+ # Train path: weighted sum of (ce, gate_aux, distill) so callers can
1038
+ # zero any term by setting its weight to 0 (e.g. ``aha_ce_weight=0``
1039
+ # to train the gate purely from ``aux + distill``).
1040
+ if not self.training:
1041
+ loss = ce_loss
1042
+ else:
1043
+ ce_weight = float(getattr(self.config, "aha_ce_weight", 1.0))
1044
+ distill_weight = float(getattr(self.config, "aha_distill_weight", 0.0))
1045
+ terms: list[torch.Tensor] = []
1046
+ if ce_loss is not None and ce_weight > 0.0:
1047
+ terms.append(ce_weight * ce_loss)
1048
+ if gate_soft_mean is not None:
1049
+ reg_weight = float(getattr(self.config, "aha_reg_weight", -1.0))
1050
+ if reg_weight >= 0.0:
1051
+ # Duo-style direct sparsity term:
1052
+ # alpha/gate near 1 means full/global attention, so penalize
1053
+ # mean gate usage directly.
1054
+ gate_aux_loss = reg_weight * gate_soft_mean
1055
+ else:
1056
+ # Legacy hinge aux: penalise only the excess above the target
1057
+ # ceiling. Below ``τ`` the gradient is zero, so CE and
1058
+ # distill freely shape where the gate opens without an
1059
+ # unbounded downward push from aux. See AHAQwen3Config
1060
+ # for the rationale and the collapse that motivated it.
1061
+ gate_target = float(getattr(self.config, "aha_gate_target", 1.0))
1062
+ excess = torch.clamp(gate_soft_mean - gate_target, min=0.0)
1063
+ gate_aux_loss = self.config.aha_lambda * excess
1064
+ terms.append(gate_aux_loss)
1065
+ if distill_loss is not None and distill_weight > 0.0:
1066
+ terms.append(distill_weight * distill_loss)
1067
+ if terms:
1068
+ loss = terms[0]
1069
+ for t in terms[1:]:
1070
+ loss = loss + t
1071
+
1072
+ return AHACausalLMOutputWithPast(
1073
+ loss=loss,
1074
+ logits=logits,
1075
+ past_key_values=outputs.past_key_values,
1076
+ ce_loss=ce_loss,
1077
+ gate_aux_loss=gate_aux_loss,
1078
+ distill_loss=distill_loss,
1079
+ gate_soft_mean=gate_soft_mean,
1080
+ gate_hard_mean=gate_hard_mean,
1081
+ )
1082
+
1083
+ @classmethod
1084
+ def from_pretrained_qwen3(cls, qwen3_path: str, aha_window_size: int = AHA_WINDOW_SIZE,
1085
+ aha_lambda: float = AHA_LAMBDA,
1086
+ aha_distill_weight: float = AHA_DISTILL_WEIGHT,
1087
+ aha_ce_weight: float = AHA_CE_WEIGHT,
1088
+ aha_gate_target: float = AHA_GATE_TARGET,
1089
+ aha_reg_weight: float = -1.0,
1090
+ aha_force_gate_value=None,
1091
+ aha_mode: str = AHA_MODE,
1092
+ duo_sink_size: int = DUO_SINK_SIZE,
1093
+ duo_recent_size: int = DUO_RECENT_SIZE,
1094
+ duo_alpha_init: float = DUO_ALPHA_INIT,
1095
+ aha_local_kind: str = AHA_LOCAL_KIND,
1096
+ aha_router_granularity: str = AHA_ROUTER_GRANULARITY,
1097
+ **kwargs):
1098
+ """Load a pretrained Qwen3 model and convert to AHA-Qwen3.
1099
+
1100
+ Initializes the gate weights in q_proj randomly (the extra num_heads outputs).
1101
+ All other weights are copied from the pretrained model.
1102
+ """
1103
+ # Load original Qwen3
1104
+ original = Qwen3ForCausalLM.from_pretrained(qwen3_path, **kwargs)
1105
+ original_config = original.config
1106
+
1107
+ # Create AHA config
1108
+ config_dict = original_config.to_dict()
1109
+ config_dict["aha_window_size"] = aha_window_size
1110
+ config_dict["aha_lambda"] = aha_lambda
1111
+ config_dict["aha_distill_weight"] = aha_distill_weight
1112
+ config_dict["aha_ce_weight"] = aha_ce_weight
1113
+ config_dict["aha_gate_target"] = aha_gate_target
1114
+ config_dict["aha_reg_weight"] = aha_reg_weight
1115
+ config_dict["aha_force_gate_value"] = aha_force_gate_value
1116
+ config_dict["aha_mode"] = aha_mode
1117
+ config_dict["duo_sink_size"] = duo_sink_size
1118
+ config_dict["duo_recent_size"] = duo_recent_size
1119
+ config_dict["duo_alpha_init"] = duo_alpha_init
1120
+ config_dict["aha_local_kind"] = aha_local_kind
1121
+ config_dict["aha_router_granularity"] = aha_router_granularity
1122
+ config_dict["model_type"] = "aha_qwen3"
1123
+ aha_config = AHAQwen3Config(**config_dict)
1124
+ aha_config._attn_implementation = kwargs.get("attn_implementation", "sdpa")
1125
+
1126
+ # Create AHA model
1127
+ aha_model = cls(aha_config)
1128
+
1129
+ # Copy weights
1130
+ # embed_tokens, lm_head, norm
1131
+ aha_model.model.embed_tokens.load_state_dict(original.model.embed_tokens.state_dict())
1132
+ aha_model.lm_head.load_state_dict(original.lm_head.state_dict())
1133
+ aha_model.model.norm.load_state_dict(original.model.norm.state_dict())
1134
+ aha_model.model.rotary_emb.load_state_dict(original.model.rotary_emb.state_dict())
1135
+
1136
+ # Copy per-layer weights
1137
+ num_heads = original_config.num_attention_heads
1138
+ head_dim = aha_config.head_dim
1139
+
1140
+ for i in range(original_config.num_hidden_layers):
1141
+ orig_layer = original.model.layers[i]
1142
+ aha_layer = aha_model.model.layers[i]
1143
+
1144
+ # MLP + norms: direct copy
1145
+ aha_layer.mlp.load_state_dict(orig_layer.mlp.state_dict())
1146
+ aha_layer.input_layernorm.load_state_dict(orig_layer.input_layernorm.state_dict())
1147
+ aha_layer.post_attention_layernorm.load_state_dict(orig_layer.post_attention_layernorm.state_dict())
1148
+
1149
+ # Attention: k_proj, v_proj, o_proj, q_norm, k_norm — direct copy
1150
+ aha_attn = aha_layer.self_attn
1151
+ orig_attn = orig_layer.self_attn
1152
+ aha_attn.k_proj.load_state_dict(orig_attn.k_proj.state_dict())
1153
+ aha_attn.v_proj.load_state_dict(orig_attn.v_proj.state_dict())
1154
+ aha_attn.o_proj.load_state_dict(orig_attn.o_proj.state_dict())
1155
+ aha_attn.q_norm.load_state_dict(orig_attn.q_norm.state_dict())
1156
+ aha_attn.k_norm.load_state_dict(orig_attn.k_norm.state_dict())
1157
+
1158
+ # q_proj:
1159
+ # - dynamic / duo_dynamic mode: copy original Q rows, init extra
1160
+ # gate rows random (~0.5).
1161
+ # - duo mode: q_proj is identical shape to base Qwen3 (no gate
1162
+ # rows), so direct load_state_dict works.
1163
+ orig_q_weight = orig_attn.q_proj.weight.data # (num_heads*head_dim, hidden_size)
1164
+ aha_q_weight = aha_attn.q_proj.weight.data
1165
+ if aha_attn._has_dyn_gate_logits:
1166
+ aha_q_weight[:num_heads * head_dim, :] = orig_q_weight
1167
+ nn.init.normal_(aha_q_weight[num_heads * head_dim:, :], mean=0.0, std=0.01)
1168
+ if orig_attn.q_proj.bias is not None and aha_attn.q_proj.bias is not None:
1169
+ aha_attn.q_proj.bias.data[:num_heads * head_dim] = orig_attn.q_proj.bias.data
1170
+ nn.init.zeros_(aha_attn.q_proj.bias.data[num_heads * head_dim:])
1171
+ else:
1172
+ aha_attn.q_proj.load_state_dict(orig_attn.q_proj.state_dict())
1173
+
1174
+ del original
1175
+ return aha_model
1176
+
1177
+ @classmethod
1178
+ def from_pretrained_aha(cls, checkpoint_path: str, **kwargs):
1179
+ """Load an AHA-Qwen3 checkpoint saved by `trainer.save_model`.
1180
+
1181
+ Supports both single-file (``model.safetensors``) and multi-shard
1182
+ (``model.safetensors.index.json`` + ``model-*-of-*.safetensors``)
1183
+ layouts. The latter is automatically used by HF when a checkpoint
1184
+ exceeds the single-file size threshold (e.g. Qwen3-1.7B+).
1185
+ """
1186
+ config = AHAQwen3Config.from_pretrained(checkpoint_path)
1187
+ config._attn_implementation = kwargs.get(
1188
+ "attn_implementation",
1189
+ getattr(config, "_attn_implementation", "sdpa"),
1190
+ )
1191
+
1192
+ model = cls(config)
1193
+
1194
+ single_path = os.path.join(checkpoint_path, "model.safetensors")
1195
+ index_path = os.path.join(checkpoint_path, "model.safetensors.index.json")
1196
+ if os.path.exists(single_path):
1197
+ state_dict = load_file(single_path)
1198
+ elif os.path.exists(index_path):
1199
+ with open(index_path) as f:
1200
+ shard_index = json.load(f)
1201
+ shard_files = sorted(set(shard_index["weight_map"].values()))
1202
+ state_dict = {}
1203
+ for shard in shard_files:
1204
+ state_dict.update(load_file(os.path.join(checkpoint_path, shard)))
1205
+ else:
1206
+ raise FileNotFoundError(
1207
+ f"No safetensors file or index found in {checkpoint_path}. "
1208
+ f"Expected either model.safetensors or model.safetensors.index.json."
1209
+ )
1210
+ missing_keys, unexpected_keys = model.load_state_dict(state_dict, strict=False)
1211
+ allowed_missing = {"lm_head.weight"} if getattr(config, "tie_word_embeddings", False) else set()
1212
+ extra_missing = set(missing_keys) - allowed_missing
1213
+ if extra_missing or unexpected_keys:
1214
+ raise RuntimeError(
1215
+ f"Unexpected checkpoint mismatch. missing={sorted(extra_missing)}, "
1216
+ f"unexpected={sorted(unexpected_keys)}"
1217
+ )
1218
+ if "lm_head.weight" in missing_keys:
1219
+ model.tie_weights()
1220
+
1221
+ _apply_low_alpha_force_full_heads_from_env(model)
1222
+
1223
+ torch_dtype = kwargs.get("torch_dtype")
1224
+ if torch_dtype is not None:
1225
+ model = model.to(dtype=torch_dtype)
1226
+ return model
outputs/l2a_style/hotstart/special_tokens_map.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>",
5
+ "<|object_ref_start|>",
6
+ "<|object_ref_end|>",
7
+ "<|box_start|>",
8
+ "<|box_end|>",
9
+ "<|quad_start|>",
10
+ "<|quad_end|>",
11
+ "<|vision_start|>",
12
+ "<|vision_end|>",
13
+ "<|vision_pad|>",
14
+ "<|image_pad|>",
15
+ "<|video_pad|>"
16
+ ],
17
+ "eos_token": {
18
+ "content": "<|im_end|>",
19
+ "lstrip": false,
20
+ "normalized": false,
21
+ "rstrip": false,
22
+ "single_word": false
23
+ },
24
+ "pad_token": {
25
+ "content": "<|endoftext|>",
26
+ "lstrip": false,
27
+ "normalized": false,
28
+ "rstrip": false,
29
+ "single_word": false
30
+ }
31
+ }
outputs/l2a_style/hotstart/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
3
+ size 11422654
outputs/l2a_style/hotstart/tokenizer_config.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>"
228
+ ],
229
+ "bos_token": null,
230
+ "clean_up_tokenization_spaces": false,
231
+ "eos_token": "<|im_end|>",
232
+ "errors": "replace",
233
+ "extra_special_tokens": {},
234
+ "model_max_length": 131072,
235
+ "pad_token": "<|endoftext|>",
236
+ "split_special_tokens": false,
237
+ "tokenizer_class": "Qwen2Tokenizer",
238
+ "unk_token": null
239
+ }
outputs/l2a_style/hotstart/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/logs/hotstart.log ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ saved=/workspace/outputs/l2a_style/hotstart init_full_prob=0.9000 gate_logit=2.197225 hard_sparsity=0.0 router_granularity=token gate_params=57,372
2
+ [W718 09:29:49.720104741 AllocatorConfig.cpp:28] Warning: PYTORCH_CUDA_ALLOC_CONF is deprecated, use PYTORCH_ALLOC_CONF instead (function operator())
outputs/l2a_style/logs/stage1.log ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [dynamic-duo] checkpoint=/workspace/outputs/l2a_style/hotstart
2
+ [dynamic-duo] loss=hidden_state_distill + 0.1 * mean(gate_soft), ce_weight=0.0 tail_frac=0.0 tail_weight=0.0
3
+ [dynamic-duo] router_granularity=token native_gate_rows=1 local_kind=sink_recent sink=64 recent=256
4
+ [dynamic-duo] router_granularity=token gate_rows/layer=1 effective trainable gate params: 57,372
5
+ [dynamic-duo] data_source=am_distilled path=/workspace/recipe/data/am_distilled_long_mix split=train n=1,024 label_mode=full max_length=8192
6
+ [step 10/300] distill=0.000000 reg=0.899967 loss=0.089997 gate_soft=0.9000 gate_hard=1.0000 lr=1.0000e-05 seq_len=8192
7
+ [step 20/300] distill=0.000000 reg=0.888791 loss=0.088879 gate_soft=0.8888 gate_hard=1.0000 lr=2.0000e-05 seq_len=8192
8
+ [step 30/300] distill=0.000067 reg=0.861061 loss=0.086174 gate_soft=0.8611 gate_hard=0.9999 lr=3.0000e-05 seq_len=8192
9
+ [step 40/300] distill=0.010408 reg=0.805766 loss=0.090984 gate_soft=0.8058 gate_hard=0.9487 lr=3.0000e-05 seq_len=8192
10
+ [step 50/300] distill=0.011854 reg=0.774085 loss=0.089263 gate_soft=0.7741 gate_hard=0.9148 lr=3.0000e-05 seq_len=8192
11
+ [step 60/300] distill=0.006979 reg=0.761850 loss=0.083164 gate_soft=0.7619 gate_hard=0.9309 lr=3.0000e-05 seq_len=2489
12
+ [step 70/300] distill=0.015172 reg=0.747514 loss=0.089924 gate_soft=0.7475 gate_hard=0.9030 lr=3.0000e-05 seq_len=8192
13
+ [step 80/300] distill=0.011069 reg=0.734405 loss=0.084509 gate_soft=0.7344 gate_hard=0.8871 lr=3.0000e-05 seq_len=543
14
+ [step 90/300] distill=0.008806 reg=0.728545 loss=0.081660 gate_soft=0.7285 gate_hard=0.8942 lr=3.0000e-05 seq_len=3013
15
+ [step 100/300] distill=0.015292 reg=0.712491 loss=0.086541 gate_soft=0.7125 gate_hard=0.8414 lr=3.0000e-05 seq_len=2760
16
+ [dynamic-duo] saved /workspace/outputs/l2a_style/stage1/checkpoint-100
17
+ [step 110/300] distill=0.017190 reg=0.703012 loss=0.087491 gate_soft=0.7030 gate_hard=0.8408 lr=3.0000e-05 seq_len=8192
18
+ [step 120/300] distill=0.010193 reg=0.704088 loss=0.080601 gate_soft=0.7041 gate_hard=0.8731 lr=3.0000e-05 seq_len=8192
19
+ [step 130/300] distill=0.015796 reg=0.696284 loss=0.085425 gate_soft=0.6963 gate_hard=0.8413 lr=3.0000e-05 seq_len=8192
20
+ [step 140/300] distill=0.017513 reg=0.685718 loss=0.086085 gate_soft=0.6857 gate_hard=0.8226 lr=3.0000e-05 seq_len=1838
21
+ [step 150/300] distill=0.011119 reg=0.689005 loss=0.080019 gate_soft=0.6890 gate_hard=0.8567 lr=3.0000e-05 seq_len=8192
22
+ [step 160/300] distill=0.015535 reg=0.681912 loss=0.083727 gate_soft=0.6819 gate_hard=0.8306 lr=3.0000e-05 seq_len=1321
23
+ [step 170/300] distill=0.015008 reg=0.675544 loss=0.082562 gate_soft=0.6755 gate_hard=0.8098 lr=3.0000e-05 seq_len=1032
24
+ [step 180/300] distill=0.015026 reg=0.668395 loss=0.081866 gate_soft=0.6684 gate_hard=0.8013 lr=3.0000e-05 seq_len=8192
25
+ [step 190/300] distill=0.015657 reg=0.664140 loss=0.082071 gate_soft=0.6641 gate_hard=0.7845 lr=3.0000e-05 seq_len=1739
26
+ [step 200/300] distill=0.018297 reg=0.660199 loss=0.084317 gate_soft=0.6602 gate_hard=0.7992 lr=3.0000e-05 seq_len=8192
27
+ [dynamic-duo] saved /workspace/outputs/l2a_style/stage1/checkpoint-200
28
+ [step 210/300] distill=0.017238 reg=0.656642 loss=0.082902 gate_soft=0.6566 gate_hard=0.7862 lr=3.0000e-05 seq_len=8192
29
+ [step 220/300] distill=0.019109 reg=0.652315 loss=0.084341 gate_soft=0.6523 gate_hard=0.7803 lr=3.0000e-05 seq_len=3719
30
+ [step 230/300] distill=0.012598 reg=0.649540 loss=0.077552 gate_soft=0.6495 gate_hard=0.7757 lr=3.0000e-05 seq_len=8192
31
+ [step 240/300] distill=0.017391 reg=0.643418 loss=0.081733 gate_soft=0.6434 gate_hard=0.7615 lr=3.0000e-05 seq_len=8192
32
+ [step 250/300] distill=0.020034 reg=0.642582 loss=0.084292 gate_soft=0.6426 gate_hard=0.7895 lr=3.0000e-05 seq_len=8192
33
+ [step 260/300] distill=0.013925 reg=0.638718 loss=0.077797 gate_soft=0.6387 gate_hard=0.7767 lr=3.0000e-05 seq_len=1387
34
+ [step 270/300] distill=0.020713 reg=0.633316 loss=0.084045 gate_soft=0.6333 gate_hard=0.7675 lr=3.0000e-05 seq_len=1318
35
+ [step 280/300] distill=0.016265 reg=0.631081 loss=0.079373 gate_soft=0.6311 gate_hard=0.7611 lr=2.1000e-05 seq_len=1686
36
+ [step 290/300] distill=0.019808 reg=0.630108 loss=0.082818 gate_soft=0.6301 gate_hard=0.7657 lr=1.1000e-05 seq_len=8192
37
+ [step 300/300] distill=0.015872 reg=0.635189 loss=0.079391 gate_soft=0.6352 gate_hard=0.7790 lr=3.0000e-06 seq_len=4081
38
+ [dynamic-duo] saved /workspace/outputs/l2a_style/stage1/checkpoint-300
39
+ [dynamic-duo] done. Final gate param stats: {'gate_weight_l2': 0.8540672063827515, 'gate_weight_abs_mean': 0.002572857541963458, 'gate_bias_mean': 2.203125, 'gate_bias_std': 0.0, 'gate_bias_alpha_mean': 0.9005298018455505, 'gate_bias_alpha_gt05': 1.0}
40
+ [W718 09:41:41.091440684 AllocatorConfig.cpp:28] Warning: PYTORCH_CUDA_ALLOC_CONF is deprecated, use PYTORCH_ALLOC_CONF instead (function operator())
outputs/l2a_style/logs/stage2.log ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
  0%| | 0/75 [00:00<?, ?it/s]
 
1
  1%|▏ | 1/75 [00:02<03:11, 2.59s/it]
 
2
  5%|▌ | 4/75 [00:05<01:29, 1.26s/it]
 
3
  7%|▋ | 5/75 [00:07<01:46, 1.52s/it]
 
4
 
 
 
5
  7%|▋ | 5/75 [00:07<01:46, 1.52s/it]
 
6
  8%|▊ | 6/75 [00:08<01:35, 1.38s/it]
 
7
  9%|▉ | 7/75 [00:09<01:26, 1.28s/it]
 
8
  12%|█▏ | 9/75 [00:12<01:23, 1.27s/it]
 
9
 
 
 
10
  13%|█▎ | 10/75 [00:12<01:22, 1.27s/it]
 
11
  15%|█▍ | 11/75 [00:14<01:21, 1.27s/it]
 
12
  16%|█▌ | 12/75 [00:16<01:32, 1.47s/it]
 
13
  17%|█▋ | 13/75 [00:19<01:41, 1.64s/it]
 
14
 
 
 
15
  20%|██ | 15/75 [00:19<01:38, 1.64s/it]
 
16
  21%|██▏ | 16/75 [00:20<00:58, 1.01it/s]
 
17
  24%|██▍ | 18/75 [00:23<01:05, 1.15s/it]
 
18
  27%|██▋ | 20/75 [00:25<01:06, 1.22s/it]
 
19
 
 
 
20
  27%|██▋ | 20/75 [00:25<01:06, 1.22s/it]
 
21
  29%|██▉ | 22/75 [00:28<01:06, 1.25s/it]
 
22
  33%|███▎ | 25/75 [00:31<00:57, 1.15s/it]
 
23
 
 
 
24
  33%|███▎ | 25/75 [00:31<00:57, 1.15s/it]
 
25
  35%|███▍ | 26/75 [00:41<02:09, 2.63s/it]
 
26
  36%|███▌ | 27/75 [00:44<02:02, 2.55s/it]
 
27
  37%|███▋ | 28/75 [00:46<01:56, 2.47s/it]
 
28
  40%|████ | 30/75 [00:48<01:30, 2.02s/it]
 
29
 
 
 
30
  40%|███��� | 30/75 [00:48<01:30, 2.02s/it]
 
31
  44%|████▍ | 33/75 [00:51<01:05, 1.55s/it]
 
32
  45%|████▌ | 34/75 [00:53<01:08, 1.66s/it]
 
33
  47%|████▋ | 35/75 [00:56<01:10, 1.77s/it]
 
34
 
 
 
35
  47%|████▋ | 35/75 [00:56<01:10, 1.77s/it]
 
36
  49%|████▉ | 37/75 [00:58<01:02, 1.64s/it]
 
37
  52%|█████▏ | 39/75 [01:01<00:54, 1.51s/it]
 
38
  53%|█████▎ | 40/75 [01:03<00:57, 1.64s/it]
 
39
 
 
 
40
  53%|█████▎ | 40/75 [01:03<00:57, 1.64s/it]
 
41
  55%|█████▍ | 41/75 [01:05<00:59, 1.76s/it]
 
42
  56%|█████▌ | 42/75 [01:08<01:01, 1.87s/it]
 
43
  57%|█████▋ | 43/75 [01:10<01:02, 1.95s/it]
 
44
  59%|█████▊ | 44/75 [01:12<01:02, 2.01s/it]
 
45
 
 
 
46
  60%|██████ | 45/75 [01:13<01:00, 2.01s/it]
 
47
  61%|██████▏ | 46/75 [01:15<00:51, 1.79s/it]
 
48
  63%|██████▎ | 47/75 [01:17<00:52, 1.89s/it]
 
49
  67%|██████▋ | 50/75 [01:20<00:36, 1.44s/it]
 
50
 
 
 
51
  67%|██████▋ | 50/75 [01:20<00:36, 1.44s/it]
 
52
  68%|██████▊ | 51/75 [01:33<01:27, 3.63s/it]
 
53
  69%|██████▉ | 52/75 [01:35<01:16, 3.32s/it]
 
54
  71%|███████ | 53/75 [01:37<01:07, 3.05s/it]
 
55
  72%|███████▏ | 54/75 [01:39<00:59, 2.84s/it]
 
56
  73%|███████▎ | 55/75 [01:42<00:53, 2.66s/it]
 
57
 
 
 
58
  73%|███████▎ | 55/75 [01:42<00:53, 2.66s/it]
 
59
  77%|███████▋ | 58/75 [01:44<00:29, 1.75s/it]
 
60
  79%|███████▊ | 59/75 [01:47<00:29, 1.84s/it]
 
61
  80%|████████ | 60/75 [01:49<00:28, 1.92s/it]
 
62
 
 
 
63
  80%|████████ | 60/75 [01:49<00:28, 1.92s/it]
 
64
  81%|████████▏ | 61/75 [01:51<00:27, 1.98s/it]
 
65
  83%|████████▎ | 62/75 [01:53<00:26, 2.04s/it]
 
66
  84%|████████▍ | 63/75 [01:55<00:24, 2.08s/it]
 
67
  85%|████████▌ | 64/75 [01:58<00:23, 2.11s/it]
 
68
  87%|████████▋ | 65/75 [02:00<00:21, 2.13s/it]
 
69
 
 
 
70
  87%|████████▋ | 65/75 [02:00<00:21, 2.13s/it]
 
71
  89%|████████▉ | 67/75 [02:03<00:14, 1.80s/it]
 
72
  91%|█████████ | 68/75 [02:05<00:13, 1.90s/it]
 
73
  92%|█████████▏| 69/75 [02:07<00:11, 1.97s/it]
 
74
 
 
 
75
  93%|█████████▎| 70/75 [02:08<00:09, 1.97s/it]
 
76
  95%|█████████▍| 71/75 [02:10<00:07, 1.77s/it]
 
77
  97%|█████████▋| 73/75 [02:13<00:03, 1.65s/it]
 
78
  99%|█████████▊| 74/75 [02:15<00:01, 1.76s/it]
 
79
 
 
 
 
80
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Loading Qwen3 from /workspace and converting to AHA-Qwen3...
2
+ aha_checkpoint_path=/workspace/outputs/l2a_style/stage1/checkpoint-300
3
+ aha_mode=dynamic, aha_router_granularity=token, aha_window_size=1024, aha_lambda=0.0003, aha_distill_weight=0.5, aha_ce_weight=1.0, aha_gate_target=1.0, aha_reg_weight=0.01, gate_only=False, duo_alpha_only=False, grouped_lr=True
4
+ dataset=/workspace/recipe/data/am_distilled_long_mix[train]
5
+ output_dir=/workspace/outputs/l2a_style/stage2
6
+ max_seq_length=8192
7
+ optimizer/schedule: lr=3.000e-06, scheduler=constant_with_warmup, warmup_ratio=0.1, betas=(0.9, 0.999), weight_decay=0.0, max_grad_norm=1.0
8
+ batch: per_device=1, grad_accum=1, world_size=1, effective_global=1
9
+ report_to=none
10
+ AHA_LOCAL_KIND override: 'sink_recent' -> 'sink_recent'
11
+ freeze_embeddings_lm_head=True
12
+ Trainable: 1,409,639,452 / 2,031,969,308 (69.4%)
13
+ Loading dataset from /workspace/recipe/data/am_distilled_long_mix...
14
+ loaded_rows=1,024
15
+ GROUPED_LR optimizer: gate_lr=3.000e-06, backbone_lr=3.000e-07, q_row_update_scale=0.1, q_proj_params=117,555,228, effective_gate_params=57,372, other_trainable=1,292,084,224
16
+ Starting training… Extra log fields: ce_loss, gate_aux_loss, loss_total (=ce+gate_aux), gate_soft_mean, gate_hard_mean, hf_loss_ratio (= key `loss` / loss_total). ce_loss / loss_total are raw model means (language quality + AHA aux). Key `loss` is HuggingFace Trainer’s gradient-accumulation-normalized aggregate; hf_loss_ratio should stay near 1.0.
17
+ Starting training...
18
+
19
+
20
  0%| | 0/75 [00:00<?, ?it/s]
21
+
22
  1%|▏ | 1/75 [00:02<03:11, 2.59s/it]
23
+
24
  5%|▌ | 4/75 [00:05<01:29, 1.26s/it]
25
+
26
  7%|▋ | 5/75 [00:07<01:46, 1.52s/it]
27
+
28
 
29
+
30
+
31
  7%|▋ | 5/75 [00:07<01:46, 1.52s/it]
32
+
33
  8%|▊ | 6/75 [00:08<01:35, 1.38s/it]
34
+
35
  9%|▉ | 7/75 [00:09<01:26, 1.28s/it]
36
+
37
  12%|█▏ | 9/75 [00:12<01:23, 1.27s/it]
38
+
39
 
40
+
41
+
42
  13%|█▎ | 10/75 [00:12<01:22, 1.27s/it]
43
+
44
  15%|█▍ | 11/75 [00:14<01:21, 1.27s/it]
45
+
46
  16%|█▌ | 12/75 [00:16<01:32, 1.47s/it]
47
+
48
  17%|█▋ | 13/75 [00:19<01:41, 1.64s/it]
49
+
50
 
51
+
52
+
53
  20%|██ | 15/75 [00:19<01:38, 1.64s/it]
54
+
55
  21%|██▏ | 16/75 [00:20<00:58, 1.01it/s]
56
+
57
  24%|██▍ | 18/75 [00:23<01:05, 1.15s/it]
58
+
59
  27%|██▋ | 20/75 [00:25<01:06, 1.22s/it]
60
+
61
 
62
+
63
+
64
  27%|██▋ | 20/75 [00:25<01:06, 1.22s/it]
65
+
66
  29%|██▉ | 22/75 [00:28<01:06, 1.25s/it]
67
+
68
  33%|███▎ | 25/75 [00:31<00:57, 1.15s/it]
69
+
70
 
71
+
72
+
73
  33%|███▎ | 25/75 [00:31<00:57, 1.15s/it]
74
+
75
  35%|███▍ | 26/75 [00:41<02:09, 2.63s/it]
76
+
77
  36%|███▌ | 27/75 [00:44<02:02, 2.55s/it]
78
+
79
  37%|███▋ | 28/75 [00:46<01:56, 2.47s/it]
80
+
81
  40%|████ | 30/75 [00:48<01:30, 2.02s/it]
82
+
83
 
84
+
85
+
86
  40%|███��� | 30/75 [00:48<01:30, 2.02s/it]
87
+
88
  44%|████▍ | 33/75 [00:51<01:05, 1.55s/it]
89
+
90
  45%|████▌ | 34/75 [00:53<01:08, 1.66s/it]
91
+
92
  47%|████▋ | 35/75 [00:56<01:10, 1.77s/it]
93
+
94
 
95
+
96
+
97
  47%|████▋ | 35/75 [00:56<01:10, 1.77s/it]
98
+
99
  49%|████▉ | 37/75 [00:58<01:02, 1.64s/it]
100
+
101
  52%|█████▏ | 39/75 [01:01<00:54, 1.51s/it]
102
+
103
  53%|█████▎ | 40/75 [01:03<00:57, 1.64s/it]
104
+
105
 
106
+
107
+
108
  53%|█████▎ | 40/75 [01:03<00:57, 1.64s/it]
109
+
110
  55%|█████▍ | 41/75 [01:05<00:59, 1.76s/it]
111
+
112
  56%|█████▌ | 42/75 [01:08<01:01, 1.87s/it]
113
+
114
  57%|█████▋ | 43/75 [01:10<01:02, 1.95s/it]
115
+
116
  59%|█████▊ | 44/75 [01:12<01:02, 2.01s/it]
117
+
118
 
119
+
120
+
121
  60%|██████ | 45/75 [01:13<01:00, 2.01s/it]
122
+
123
  61%|██████▏ | 46/75 [01:15<00:51, 1.79s/it]
124
+
125
  63%|██████▎ | 47/75 [01:17<00:52, 1.89s/it]
126
+
127
  67%|██████▋ | 50/75 [01:20<00:36, 1.44s/it]
128
+
129
 
130
+
131
+
132
  67%|██████▋ | 50/75 [01:20<00:36, 1.44s/it]
133
+
134
  68%|██████▊ | 51/75 [01:33<01:27, 3.63s/it]
135
+
136
  69%|██████▉ | 52/75 [01:35<01:16, 3.32s/it]
137
+
138
  71%|███████ | 53/75 [01:37<01:07, 3.05s/it]
139
+
140
  72%|███████▏ | 54/75 [01:39<00:59, 2.84s/it]
141
+
142
  73%|███████▎ | 55/75 [01:42<00:53, 2.66s/it]
143
+
144
 
145
+
146
+
147
  73%|███████▎ | 55/75 [01:42<00:53, 2.66s/it]
148
+
149
  77%|███████▋ | 58/75 [01:44<00:29, 1.75s/it]
150
+
151
  79%|███████▊ | 59/75 [01:47<00:29, 1.84s/it]
152
+
153
  80%|████████ | 60/75 [01:49<00:28, 1.92s/it]
154
+
155
 
156
+
157
+
158
  80%|████████ | 60/75 [01:49<00:28, 1.92s/it]
159
+
160
  81%|████████▏ | 61/75 [01:51<00:27, 1.98s/it]
161
+
162
  83%|████████▎ | 62/75 [01:53<00:26, 2.04s/it]
163
+
164
  84%|████████▍ | 63/75 [01:55<00:24, 2.08s/it]
165
+
166
  85%|████████▌ | 64/75 [01:58<00:23, 2.11s/it]
167
+
168
  87%|████████▋ | 65/75 [02:00<00:21, 2.13s/it]
169
+
170
 
171
+
172
+
173
  87%|████████▋ | 65/75 [02:00<00:21, 2.13s/it]
174
+
175
  89%|████████▉ | 67/75 [02:03<00:14, 1.80s/it]
176
+
177
  91%|█████████ | 68/75 [02:05<00:13, 1.90s/it]
178
+
179
  92%|█████████▏| 69/75 [02:07<00:11, 1.97s/it]
180
+
181
 
182
+
183
+
184
  93%|█████████▎| 70/75 [02:08<00:09, 1.97s/it]
185
+
186
  95%|█████████▍| 71/75 [02:10<00:07, 1.77s/it]
187
+
188
  97%|█████████▋| 73/75 [02:13<00:03, 1.65s/it]
189
+
190
  99%|█████████▊| 74/75 [02:15<00:01, 1.76s/it]
191
+
192
 
193
+
194
+
195
+
196
 
197
+
198
+
199
+ ***** train metrics *****
200
+ total_flos = 4246389GF
201
+ train_loss = 1.0416
202
+ train_runtime = 0:02:27.18
203
+ train_samples_per_second = 0.51
204
+ train_steps_per_second = 0.51
205
+ Training completed. Metrics: {'train_runtime': 147.1816, 'train_samples_per_second': 0.51, 'train_steps_per_second': 0.51, 'total_flos': 4559525883858240.0, 'train_loss': 1.0415593115488688}
206
+ [W718 09:44:51.502781707 AllocatorConfig.cpp:28] Warning: PYTORCH_CUDA_ALLOC_CONF is deprecated, use PYTORCH_ALLOC_CONF instead (function operator())
outputs/l2a_style/stage1/checkpoint-100/added_tokens.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|box_end|>": 151649,
9
+ "<|box_start|>": 151648,
10
+ "<|endoftext|>": 151643,
11
+ "<|file_sep|>": 151664,
12
+ "<|fim_middle|>": 151660,
13
+ "<|fim_pad|>": 151662,
14
+ "<|fim_prefix|>": 151659,
15
+ "<|fim_suffix|>": 151661,
16
+ "<|im_end|>": 151645,
17
+ "<|im_start|>": 151644,
18
+ "<|image_pad|>": 151655,
19
+ "<|object_ref_end|>": 151647,
20
+ "<|object_ref_start|>": 151646,
21
+ "<|quad_end|>": 151651,
22
+ "<|quad_start|>": 151650,
23
+ "<|repo_name|>": 151663,
24
+ "<|video_pad|>": 151656,
25
+ "<|vision_end|>": 151653,
26
+ "<|vision_pad|>": 151654,
27
+ "<|vision_start|>": 151652
28
+ }
outputs/l2a_style/stage1/checkpoint-100/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 %}
outputs/l2a_style/stage1/checkpoint-100/config.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_ce_weight": 0.0,
3
+ "aha_distill_weight": 0.0,
4
+ "aha_force_gate_value": null,
5
+ "aha_gate_init_full_prob": 0.9,
6
+ "aha_gate_target": 0.7,
7
+ "aha_hotstart_source": "/workspace",
8
+ "aha_lambda": 0.0003,
9
+ "aha_local_kind": "sink_recent",
10
+ "aha_mode": "dynamic",
11
+ "aha_reg_weight": -1.0,
12
+ "aha_router_granularity": "token",
13
+ "aha_window_size": 128,
14
+ "architectures": [
15
+ "AHAQwen3ForCausalLM"
16
+ ],
17
+ "attention_bias": true,
18
+ "attention_dropout": 0.0,
19
+ "auto_map": {
20
+ "AutoConfig": "modeling_aha_qwen3.AHAQwen3Config",
21
+ "AutoModelForCausalLM": "modeling_aha_qwen3.AHAQwen3ForCausalLM"
22
+ },
23
+ "bos_token_id": 151643,
24
+ "duo_alpha_init": 1.0,
25
+ "duo_recent_size": 256,
26
+ "duo_sink_size": 64,
27
+ "eos_token_id": 151645,
28
+ "head_dim": 128,
29
+ "hidden_act": "silu",
30
+ "hidden_size": 2048,
31
+ "initializer_range": 0.02,
32
+ "intermediate_size": 6144,
33
+ "layer_types": [
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
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention",
57
+ "full_attention",
58
+ "full_attention",
59
+ "full_attention",
60
+ "full_attention",
61
+ "full_attention"
62
+ ],
63
+ "max_position_embeddings": 40960,
64
+ "max_window_layers": 28,
65
+ "model_type": "aha_qwen3",
66
+ "num_attention_heads": 16,
67
+ "num_hidden_layers": 28,
68
+ "num_key_value_heads": 8,
69
+ "rms_norm_eps": 1e-06,
70
+ "rope_scaling": null,
71
+ "rope_theta": 1000000,
72
+ "sliding_window": null,
73
+ "tie_word_embeddings": false,
74
+ "torch_dtype": "bfloat16",
75
+ "transformers_version": "4.54.0",
76
+ "use_cache": false,
77
+ "use_sliding_window": false,
78
+ "vocab_size": 151936
79
+ }
outputs/l2a_style/stage1/checkpoint-100/dynamic_duo_state.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 100,
3
+ "router_granularity": "token",
4
+ "native_gate_rows_per_layer": 1,
5
+ "effective_sparsity_denominator": "token x KV-head x layer",
6
+ "gate_soft_mean": 0.6977670788764954,
7
+ "gate_soft_std": 0.2190520465373993,
8
+ "gate_hard_mean": 0.7740036249160767,
9
+ "gate_min": 0.005218505859375,
10
+ "gate_max": 0.91796875,
11
+ "gate_weight_l2": 0.28687241673469543,
12
+ "gate_weight_abs_mean": 0.0009648351115174592,
13
+ "gate_bias_mean": 2.203125,
14
+ "gate_bias_std": 0.0,
15
+ "gate_bias_alpha_mean": 0.9005298018455505,
16
+ "gate_bias_alpha_gt05": 1.0
17
+ }
outputs/l2a_style/stage1/checkpoint-100/dynamic_duo_train_args.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_checkpoint": "/workspace/outputs/l2a_style/hotstart",
3
+ "model_path": "/workspace",
4
+ "output_dir": "/workspace/outputs/l2a_style/stage1",
5
+ "am_dataset_path": "/workspace/recipe/data/am_distilled_long_mix",
6
+ "am_dataset_split": "train",
7
+ "data_source": "am_distilled",
8
+ "am_label_mode": "full",
9
+ "longbench_tasks": [
10
+ "passage_retrieval_en",
11
+ "multifieldqa_en",
12
+ "qasper",
13
+ "2wikimqa"
14
+ ],
15
+ "longbench_samples_per_task": 30,
16
+ "longbench_cache_dir": "/workspace/AHA/AHA-Qwen3/data/longbench_cache",
17
+ "max_length": 8192,
18
+ "num_steps": 300,
19
+ "warmup_ratio": 0.1,
20
+ "lr": 3e-05,
21
+ "reg_weight": 0.1,
22
+ "reg_head_weight_mode": "uniform",
23
+ "reg_head_weight_power": 1.0,
24
+ "ce_weight": 0.0,
25
+ "distill_tail_frac": 0.0,
26
+ "distill_tail_weight": 0.0,
27
+ "batch_size": 1,
28
+ "grad_accum": 1,
29
+ "save_steps": 100,
30
+ "log_steps": 10,
31
+ "seed": 42,
32
+ "dtype": "bfloat16",
33
+ "attn_impl": "sdpa",
34
+ "aha_local_kind": "sink_recent",
35
+ "router_granularity": "token"
36
+ }
outputs/l2a_style/stage1/checkpoint-100/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 151643,
4
+ "eos_token_id": 151645,
5
+ "transformers_version": "4.54.0",
6
+ "use_cache": false
7
+ }
outputs/l2a_style/stage1/checkpoint-100/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/stage1/checkpoint-100/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5926a556f95fad8143e233896bd5c980c5ea4a07a0a25db74152fb832a7b3ede
3
+ size 4063986840
outputs/l2a_style/stage1/checkpoint-100/modeling_aha_qwen3.py ADDED
@@ -0,0 +1,1226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AHA-Qwen3: All-or-Here Attention applied to Qwen3.
3
+
4
+ Minimal modification to standard Qwen3 attention: adds a binary gate that
5
+ dynamically toggles between full attention and local attention. The persisted
6
+ ``aha_router_granularity`` config selects either one gate per (token, KV-head)
7
+ or one gate per token shared by every head in the layer.
8
+
9
+ Based on: transformers.models.qwen3.modeling_qwen3 (Qwen3Attention)
10
+ Changes:
11
+ 1. q_proj outputs either one extra gate value (``token``) or
12
+ ``num_key_value_heads`` values (``token_kv_head``). The shared-token
13
+ value is broadcast to all KV/query heads before attention mixing.
14
+ 2. Gate: sigmoid → hard threshold → STE for training
15
+ 3. Double-mix: global attention + local window attention, blended by
16
+ gate (broadcast across the GQA group)
17
+ 4. Auxiliary loss: λ * mean(gate_soft) to encourage sparsity
18
+ """
19
+
20
+ import atexit
21
+ import json
22
+ import os
23
+ from dataclasses import dataclass
24
+ from pathlib import Path
25
+ from typing import Callable, Optional, Union
26
+
27
+ import torch
28
+ import torch.nn as nn
29
+ from safetensors.torch import load_file
30
+
31
+ from transformers import Qwen3Config, Qwen3ForCausalLM
32
+ from transformers.modeling_layers import GradientCheckpointingLayer
33
+ from transformers.models.qwen3.modeling_qwen3 import (
34
+ Qwen3Attention,
35
+ Qwen3DecoderLayer,
36
+ Qwen3Model,
37
+ Qwen3RMSNorm,
38
+ apply_rotary_pos_emb,
39
+ )
40
+ from transformers.cache_utils import Cache, DynamicCache
41
+ from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
42
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
43
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
44
+ from transformers.processing_utils import Unpack
45
+ from transformers.utils.generic import TransformersKwargs
46
+
47
+ # AHA constants
48
+ AHA_WINDOW_SIZE = 1024
49
+ AHA_LAMBDA = 3e-4
50
+ AHA_DISTILL_WEIGHT = 0.0
51
+ AHA_CE_WEIGHT = 1.0
52
+ # Gate-density ceiling (hinge target). The aux loss only fires when
53
+ # ``mean(gate_soft) > AHA_GATE_TARGET``, so sparsity cannot drop
54
+ # below ``1 - AHA_GATE_TARGET`` in steady state regardless of how
55
+ # large ``aha_lambda`` is. Setting this to 1.0 recovers the legacy
56
+ # behaviour (unconditional downward pressure).
57
+ AHA_GATE_TARGET = 1.0
58
+
59
+ # DuoAttention baseline knobs. When ``aha_mode == "duo"`` the per-(token,
60
+ # kv_head) gate is replaced by a per-(layer, kv_head) learnable scalar
61
+ # (``duo_alpha``, 224 scalars total for Qwen3-0.6B), and the "local"
62
+ # branch becomes a streaming mask with ``duo_sink_size`` attention sinks
63
+ # plus a recent window of ``duo_recent_size``. This is a strict reduction
64
+ # of the dynamic AHA gate and reproduces the DuoAttention paper's head
65
+ # classifier (Xiao et al. 2024) on the Qwen3 backbone.
66
+ AHA_MODE = "dynamic"
67
+ DUO_SINK_SIZE = 64
68
+ DUO_RECENT_SIZE = 256
69
+ DUO_ALPHA_INIT = 1.0
70
+ # Local-branch mask kind for the dual-branch attention (global vs local).
71
+ # - "sliding_window": last ``aha_window_size`` tokens, no attention sinks.
72
+ # Historical default for ``aha_mode="dynamic"`` (Bv3 was trained with this).
73
+ # - "sink_recent": first ``duo_sink_size`` tokens + last ``duo_recent_size``
74
+ # tokens, matching the DuoAttention paper. Historical hardcoded default for
75
+ # ``aha_mode="duo"`` (still forced for duo regardless of this field).
76
+ # § 9.4.13.3 found that hot-starting a dynamic-mode ckpt from a duo ckpt with
77
+ # sliding_window local loses ~20pp LongBench retention vs the duo source,
78
+ # almost entirely from passage_retrieval_en (sinks are critical for long
79
+ # passage retrieval). Setting this to "sink_recent" for hot-started dynamic
80
+ # ckpts closes that architectural gap.
81
+ AHA_LOCAL_KIND = "sliding_window"
82
+ AHA_ROUTER_GRANULARITY = "token_kv_head"
83
+
84
+
85
+ def aha_router_output_size(config: Qwen3Config) -> int:
86
+ """Return the number of native dynamic-router logits per token."""
87
+
88
+ granularity = getattr(config, "aha_router_granularity", AHA_ROUTER_GRANULARITY)
89
+ if granularity == "token":
90
+ return 1
91
+ if granularity == "token_kv_head":
92
+ return int(config.num_key_value_heads)
93
+ raise ValueError(f"Unsupported aha_router_granularity={granularity!r}")
94
+
95
+
96
+ class _AHAInferenceSparsityTracker:
97
+ """Accumulate exact inference hard-route counts without changing outputs."""
98
+
99
+ def __init__(self) -> None:
100
+ # Keep counters as device scalars until process exit. Calling .item()
101
+ # once per layer and decode token serialized CUDA and made the
102
+ # diagnostic tracker materially slow generation.
103
+ self.sparse_decisions: dict[int, torch.Tensor] = {}
104
+ self.total_decisions: dict[int, int] = {}
105
+ self.phase_sparse_decisions: dict[str, torch.Tensor] = {}
106
+ self.phase_total_decisions: dict[str, int] = {}
107
+ raw_thresholds = os.environ.get("AHA_COUNTERFACTUAL_THRESHOLDS", "")
108
+ self.counterfactual_thresholds = tuple(
109
+ sorted({float(value) for value in raw_thresholds.split(",") if value.strip()})
110
+ )
111
+ if any(value < 0.0 or value > 1.0 for value in self.counterfactual_thresholds):
112
+ raise ValueError("AHA_COUNTERFACTUAL_THRESHOLDS values must be in [0, 1]")
113
+ self.counterfactual_sparse: dict[float, torch.Tensor] = {}
114
+ self.router_granularity: Optional[str] = None
115
+ self.native_router_decisions = 0
116
+ atexit.register(self.write_stats)
117
+
118
+ def update(
119
+ self,
120
+ gate_hard: torch.Tensor,
121
+ gate_soft: torch.Tensor,
122
+ layer_idx: int,
123
+ phase: str,
124
+ router_granularity: str,
125
+ native_router_width: int,
126
+ ) -> None:
127
+ if self.router_granularity is None:
128
+ self.router_granularity = router_granularity
129
+ elif self.router_granularity != router_granularity:
130
+ raise RuntimeError(
131
+ "AHA sparsity tracker received mixed router granularities: "
132
+ f"{self.router_granularity!r} and {router_granularity!r}"
133
+ )
134
+ sparse = torch.count_nonzero(gate_hard == 0).to(torch.int64)
135
+ total = int(gate_hard.numel())
136
+ effective_width = int(gate_hard.shape[-1])
137
+ self.native_router_decisions += total // effective_width * native_router_width
138
+ if layer_idx not in self.sparse_decisions:
139
+ self.sparse_decisions[layer_idx] = sparse
140
+ else:
141
+ self.sparse_decisions[layer_idx].add_(sparse)
142
+ self.total_decisions[layer_idx] = self.total_decisions.get(layer_idx, 0) + total
143
+ if phase not in self.phase_sparse_decisions:
144
+ self.phase_sparse_decisions[phase] = sparse.clone()
145
+ else:
146
+ self.phase_sparse_decisions[phase].add_(sparse)
147
+ self.phase_total_decisions[phase] = self.phase_total_decisions.get(phase, 0) + total
148
+ if self.counterfactual_thresholds:
149
+ flat = gate_soft.detach().reshape(-1, 1)
150
+ thresholds = flat.new_tensor(self.counterfactual_thresholds).reshape(1, -1)
151
+ counts = (flat <= thresholds).sum(dim=0, dtype=torch.int64)
152
+ for threshold, count in zip(self.counterfactual_thresholds, counts.unbind()):
153
+ if threshold not in self.counterfactual_sparse:
154
+ self.counterfactual_sparse[threshold] = count
155
+ else:
156
+ self.counterfactual_sparse[threshold].add_(count)
157
+
158
+ def write_stats(self) -> None:
159
+ output = os.environ.get("AHA_SPARSITY_STATS_PATH")
160
+ if not output:
161
+ return
162
+ sparse_by_layer = {
163
+ layer_idx: int(value.item())
164
+ for layer_idx, value in self.sparse_decisions.items()
165
+ }
166
+ sparse = sum(sparse_by_layer.values())
167
+ total = sum(self.total_decisions.values())
168
+ per_layer = {}
169
+ for layer_idx in sorted(self.total_decisions):
170
+ layer_sparse = sparse_by_layer.get(layer_idx, 0)
171
+ layer_total = self.total_decisions[layer_idx]
172
+ per_layer[str(layer_idx)] = {
173
+ "sparse_decisions": layer_sparse,
174
+ "total_decisions": layer_total,
175
+ "sparsity": layer_sparse / layer_total if layer_total else None,
176
+ }
177
+ payload = {
178
+ "definition": (
179
+ "hard AHA gate zeros / all token x KV-head x layer decisions, "
180
+ "token-weighted over prefill and decode"
181
+ ),
182
+ "router_granularity": self.router_granularity,
183
+ "native_router_decisions": self.native_router_decisions,
184
+ "effective_router_decisions": total,
185
+ "native_router_note": (
186
+ "Native decisions count learned gate logits before broadcast; effective "
187
+ "decisions always use token x KV-head x layer for matched sparsity."
188
+ ),
189
+ "sparse_decisions": sparse,
190
+ "total_decisions": total,
191
+ "sparsity": sparse / total if total else None,
192
+ "full_attention_usage": 1.0 - sparse / total if total else None,
193
+ "per_layer": per_layer,
194
+ "by_phase": {
195
+ phase: {
196
+ "sparse_decisions": int(self.phase_sparse_decisions[phase].item()),
197
+ "total_decisions": phase_total,
198
+ "sparsity": int(self.phase_sparse_decisions[phase].item()) / phase_total
199
+ if phase_total else None,
200
+ "full_attention_usage": 1.0
201
+ - int(self.phase_sparse_decisions[phase].item()) / phase_total
202
+ if phase_total else None,
203
+ }
204
+ for phase, phase_total in sorted(self.phase_total_decisions.items())
205
+ },
206
+ }
207
+ if self.counterfactual_thresholds:
208
+ payload["counterfactual_sparsity_by_threshold"] = {
209
+ f"{threshold:.6g}": {
210
+ "sparse_decisions": int(self.counterfactual_sparse[threshold].item()),
211
+ "total_decisions": total,
212
+ "sparsity": int(self.counterfactual_sparse[threshold].item()) / total
213
+ if total else None,
214
+ }
215
+ for threshold in self.counterfactual_thresholds
216
+ }
217
+ payload["counterfactual_note"] = (
218
+ "Routing counts only; benchmark quality must be measured in a separate "
219
+ "run with AHA_GATE_HARD_THRESHOLD set to the selected threshold."
220
+ )
221
+ path = Path(output)
222
+ path.parent.mkdir(parents=True, exist_ok=True)
223
+ path.write_text(json.dumps(payload, indent=2) + "\n")
224
+
225
+
226
+ _AHA_INFERENCE_SPARSITY_TRACKER: Optional[_AHAInferenceSparsityTracker] = None
227
+
228
+
229
+ def _track_aha_inference_sparsity(
230
+ gate_hard: torch.Tensor,
231
+ gate_soft: torch.Tensor,
232
+ layer_idx: int,
233
+ phase: str,
234
+ router_granularity: str,
235
+ native_router_width: int,
236
+ ) -> None:
237
+ global _AHA_INFERENCE_SPARSITY_TRACKER
238
+ if not os.environ.get("AHA_SPARSITY_STATS_PATH"):
239
+ return
240
+ if _AHA_INFERENCE_SPARSITY_TRACKER is None:
241
+ _AHA_INFERENCE_SPARSITY_TRACKER = _AHAInferenceSparsityTracker()
242
+ _AHA_INFERENCE_SPARSITY_TRACKER.update(
243
+ gate_hard.detach(), gate_soft.detach(), layer_idx, phase,
244
+ router_granularity, native_router_width,
245
+ )
246
+
247
+
248
+ def _get_optional_nonnegative_int_env(name: str) -> Optional[int]:
249
+ raw = os.environ.get(name, "").strip()
250
+ if not raw:
251
+ return None
252
+ value = int(raw)
253
+ if value < 0:
254
+ raise ValueError(f"{name} must be non-negative, got {value}")
255
+ return value
256
+
257
+
258
+ def _apply_low_alpha_force_full_heads_from_env(model: nn.Module) -> None:
259
+ """Auto-protect the weakest static-full heads in duo_dynamic mode.
260
+
261
+ ``AHA_FORCE_LOW_ALPHA_FULL_HEADS=K`` selects the K smallest
262
+ ``full_attention_heads`` values among heads that static Duo still kept
263
+ full (alpha > 0.5). These are the fragile boundary full heads: raising the
264
+ dynamic threshold can close them, but static Duo's alpha says they should
265
+ not be fully discarded. The selected heads are OR-ed with any manually
266
+ supplied ``AHA_FORCE_FULL_HEADS`` mask.
267
+ """
268
+ k = _get_optional_nonnegative_int_env("AHA_FORCE_LOW_ALPHA_FULL_HEADS")
269
+ if not k:
270
+ return
271
+ if getattr(model.config, "aha_mode", "dynamic") != "duo_dynamic":
272
+ return
273
+
274
+ candidates: list[tuple[float, int, int]] = []
275
+ with torch.no_grad():
276
+ for layer_idx, layer in enumerate(model.model.layers):
277
+ attn = layer.self_attn
278
+ alpha = getattr(attn, "full_attention_heads", None)
279
+ if alpha is None:
280
+ continue
281
+ for head_idx, value in enumerate(alpha.detach().float().cpu().tolist()):
282
+ if value > 0.5:
283
+ candidates.append((float(value), layer_idx, head_idx))
284
+ selected = sorted(candidates)[: min(k, len(candidates))]
285
+ if not selected:
286
+ return
287
+
288
+ selected_by_layer: dict[int, list[int]] = {}
289
+ for _, layer_idx, head_idx in selected:
290
+ selected_by_layer.setdefault(layer_idx, []).append(head_idx)
291
+ with torch.no_grad():
292
+ for layer_idx, heads in selected_by_layer.items():
293
+ mask = model.model.layers[layer_idx].self_attn._aha_force_full_heads_mask
294
+ for head_idx in heads:
295
+ mask[head_idx] = True
296
+ model.config.aha_force_low_alpha_full_heads = int(k)
297
+ formatted = ",".join(f"{layer}:{head}" for _, layer, head in selected)
298
+ print(
299
+ f"[AHA] AHA_FORCE_LOW_ALPHA_FULL_HEADS={k} selected {len(selected)} "
300
+ f"duo_dynamic heads: {formatted}",
301
+ flush=True,
302
+ )
303
+
304
+
305
+ class AHAQwen3Config(Qwen3Config):
306
+ """Qwen3Config with AHA-specific parameters."""
307
+ model_type = "aha_qwen3"
308
+
309
+ def __init__(
310
+ self,
311
+ aha_window_size=AHA_WINDOW_SIZE,
312
+ aha_lambda=AHA_LAMBDA,
313
+ aha_distill_weight=AHA_DISTILL_WEIGHT,
314
+ aha_ce_weight=AHA_CE_WEIGHT,
315
+ aha_gate_target=AHA_GATE_TARGET,
316
+ aha_reg_weight=-1.0,
317
+ aha_force_gate_value=None,
318
+ aha_mode=AHA_MODE,
319
+ duo_sink_size=DUO_SINK_SIZE,
320
+ duo_recent_size=DUO_RECENT_SIZE,
321
+ duo_alpha_init=DUO_ALPHA_INIT,
322
+ aha_local_kind=AHA_LOCAL_KIND,
323
+ aha_router_granularity=AHA_ROUTER_GRANULARITY,
324
+ **kwargs,
325
+ ):
326
+ super().__init__(**kwargs)
327
+ self.aha_window_size = aha_window_size
328
+ self.aha_lambda = aha_lambda
329
+ # Per-layer attention distillation weight. When > 0 and model is
330
+ # training, each layer contributes ``mean(((1-g)·(global-local))**2)``
331
+ # to the total loss. This is a dense per-(token, head) signal that
332
+ # pushes the gate toward 1 where SWA is insufficient to approximate
333
+ # full attention. Cost: ~0 extra forward compute (global/local are
334
+ # already computed for the mix).
335
+ self.aha_distill_weight = aha_distill_weight
336
+ # Language-modeling CE weight for the training objective. Default 1.0
337
+ # keeps the standard SFT behaviour. Set to 0.0 to drop CE entirely
338
+ # and train the gate purely from ``aux + distill`` (useful when the
339
+ # backbone is frozen and we only want to shape the gate). Evaluation
340
+ # always reports unweighted ``ce_loss`` regardless of this setting.
341
+ self.aha_ce_weight = aha_ce_weight
342
+ # Direct Duo-style sparsity regularizer. When >= 0, this replaces the
343
+ # legacy hinge term and uses ``aha_reg_weight * mean(gate_soft)``.
344
+ # Default -1 keeps older checkpoints on their original hinge objective.
345
+ self.aha_reg_weight = float(aha_reg_weight)
346
+ # Optional override used by Duo-style dynamic distillation. When set
347
+ # to 1.0, dynamic mode behaves as a full-attention teacher for one
348
+ # forward; None keeps the learned gate.
349
+ self.aha_force_gate_value = aha_force_gate_value
350
+ # Target ceiling on mean(gate_soft). Aux loss is hinge-shaped:
351
+ # ``λ · max(0, mean(gate_soft) - τ)``. Setting τ < 1.0 guarantees
352
+ # the gate cannot drop below ``1 - τ`` local-attention fraction
353
+ # in steady state -- the failure mode we observed with the legacy
354
+ # unconditional aux, where ``λ = 1.0`` drove mean(gate_hard) to
355
+ # 0.005 (99.5% local) and killed long-context accuracy
356
+ # (gsm8k 74% -> 19%). Default 1.0 is a no-op for backward
357
+ # compatibility with checkpoints trained on the old loss.
358
+ self.aha_gate_target = aha_gate_target
359
+ # DuoAttention-style baseline. ``dynamic`` keeps the original per-
360
+ # (token, kv_head) MLP gate. ``duo`` replaces it with a per-(layer,
361
+ # kv_head) learnable scalar and swaps the local branch's mask for a
362
+ # streaming (sink + recent) pattern. ``duo_dynamic`` keeps the Duo
363
+ # static mask for KV-capacity planning, but applies a dynamic AHA gate
364
+ # only on the static-full heads: effective_full = duo_full AND dyn_gate.
365
+ # Sink / recent sizes are fixed at train time; set ``duo_sink_size=0``
366
+ # for the sink-ablation run.
367
+ if aha_mode not in ("dynamic", "duo", "duo_dynamic"):
368
+ raise ValueError(
369
+ f"aha_mode must be 'dynamic', 'duo', or 'duo_dynamic', got {aha_mode!r}"
370
+ )
371
+ self.aha_mode = aha_mode
372
+ if aha_router_granularity not in ("token", "token_kv_head"):
373
+ raise ValueError(
374
+ "aha_router_granularity must be 'token' or 'token_kv_head', "
375
+ f"got {aha_router_granularity!r}"
376
+ )
377
+ # Persist this even for static Duo checkpoints. Dynamic AHA uses it to
378
+ # choose the native q_proj gate-row count; old configs omit the field
379
+ # and therefore retain the historical token_kv_head behavior.
380
+ self.aha_router_granularity = aha_router_granularity
381
+ self.duo_sink_size = int(duo_sink_size)
382
+ self.duo_recent_size = int(duo_recent_size)
383
+ self.duo_alpha_init = float(duo_alpha_init)
384
+ if aha_local_kind not in ("sliding_window", "sink_recent"):
385
+ raise ValueError(
386
+ f"aha_local_kind must be 'sliding_window' or 'sink_recent', "
387
+ f"got {aha_local_kind!r}"
388
+ )
389
+ # When aha_mode="duo" the local branch is always sink+recent regardless
390
+ # of this field (forced to the historical DuoAttention behaviour). The
391
+ # field only controls the dynamic mode's local mask.
392
+ self.aha_local_kind = aha_local_kind
393
+
394
+
395
+ def _build_streaming_causal_mask(
396
+ seq_len_q: int,
397
+ seq_len_kv: int,
398
+ sink_size: int,
399
+ recent_size: int,
400
+ device: torch.device,
401
+ dtype: torch.dtype,
402
+ kv_offset: int = 0,
403
+ ) -> torch.Tensor:
404
+ """Construct a streaming attention mask (sink + recent, causal).
405
+
406
+ Returns an additive mask of shape ``[1, 1, Q, K]`` where
407
+ disallowed positions are ``finfo(dtype).min`` and allowed positions
408
+ are ``0``. A query at position ``q`` (absolute in the sequence,
409
+ with ``kv_offset`` tokens already in the KV cache) attends to a key
410
+ at position ``k`` iff ``k <= q_abs`` AND (``k < sink_size`` OR
411
+ ``q_abs - k < recent_size``).
412
+ """
413
+ q_abs = torch.arange(seq_len_q, device=device).unsqueeze(-1) + kv_offset # [Q, 1]
414
+ k_abs = torch.arange(seq_len_kv, device=device).unsqueeze(0) # [1, K]
415
+ causal = k_abs <= q_abs
416
+ sink = k_abs < sink_size if sink_size > 0 else torch.zeros_like(causal)
417
+ recent = (q_abs - k_abs) < recent_size
418
+ allowed = causal & (sink | recent)
419
+ mask = torch.zeros(seq_len_q, seq_len_kv, dtype=dtype, device=device)
420
+ mask.masked_fill_(~allowed, torch.finfo(dtype).min)
421
+ return mask.unsqueeze(0).unsqueeze(0)
422
+
423
+
424
+ class AHAQwen3Attention(nn.Module):
425
+ """Qwen3 attention with AHA gate for per-head local/global routing."""
426
+
427
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
428
+ super().__init__()
429
+ self.config = config
430
+ self.layer_idx = layer_idx
431
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
432
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
433
+ self.scaling = self.head_dim ** -0.5
434
+ self.attention_dropout = config.attention_dropout
435
+ self.is_causal = True
436
+ self.aha_window_size = config.aha_window_size
437
+
438
+ # q_proj: extra router outputs for either one shared per-token gate or
439
+ # one per-(token, KV-head) gate.
440
+ # DUO mode uses a per-(layer, kv_head) static scalar instead, so
441
+ # q_proj keeps its vanilla Qwen3 shape and is binary-compatible
442
+ # with the base model checkpoint.
443
+ self._has_dyn_gate_logits = getattr(config, "aha_mode", "dynamic") in (
444
+ "dynamic",
445
+ "duo_dynamic",
446
+ )
447
+ self.aha_router_outputs = aha_router_output_size(config)
448
+ q_out_dim = config.num_attention_heads * self.head_dim
449
+ if self._has_dyn_gate_logits:
450
+ q_out_dim += self.aha_router_outputs
451
+ self.q_proj = nn.Linear(
452
+ config.hidden_size,
453
+ q_out_dim,
454
+ bias=config.attention_bias,
455
+ )
456
+ self.k_proj = nn.Linear(
457
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
458
+ )
459
+ self.v_proj = nn.Linear(
460
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
461
+ )
462
+ self.o_proj = nn.Linear(
463
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
464
+ )
465
+ # Qwen3 applies q_norm/k_norm per head_dim (not full projection)
466
+ self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
467
+ self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
468
+
469
+ # DuoAttention baseline: per-(layer, kv_head) learnable scalar.
470
+ # Populated in ``duo`` and ``duo_dynamic``. In ``duo_dynamic`` this
471
+ # scalar is the frozen Duo capacity mask; the dynamic gate is only
472
+ # allowed to close heads where this scalar says "full". Registered
473
+ # as ``full_attention_heads`` to match the name used in the
474
+ # official DuoAttention repo (``duo_attn.patch.llama``), so
475
+ # per-head statistics (e.g. retrieval heads vs streaming heads)
476
+ # can be read back with the same tooling.
477
+ if getattr(config, "aha_mode", "dynamic") in ("duo", "duo_dynamic"):
478
+ init = float(getattr(config, "duo_alpha_init", 1.0))
479
+ self.full_attention_heads = nn.Parameter(
480
+ torch.full((config.num_key_value_heads,), init, dtype=torch.float32)
481
+ )
482
+ else:
483
+ self.full_attention_heads = None
484
+ force_full_mask = torch.zeros(config.num_key_value_heads, dtype=torch.bool)
485
+ for raw in os.environ.get("AHA_FORCE_FULL_HEADS", "").split(","):
486
+ raw = raw.strip()
487
+ if not raw:
488
+ continue
489
+ layer_s, head_s = raw.split(":", 1)
490
+ if int(layer_s) == int(layer_idx):
491
+ force_full_mask[int(head_s)] = True
492
+ self.register_buffer(
493
+ "_aha_force_full_heads_mask",
494
+ force_full_mask,
495
+ persistent=False,
496
+ )
497
+
498
+ def forward(
499
+ self,
500
+ hidden_states: torch.Tensor,
501
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
502
+ attention_mask: Optional[dict], # dict with "global" and "local" keys
503
+ past_key_values: Optional[Cache] = None,
504
+ cache_position: Optional[torch.LongTensor] = None,
505
+ **kwargs,
506
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
507
+ input_shape = hidden_states.shape[:-1]
508
+ hidden_shape = (*input_shape, -1, self.head_dim)
509
+
510
+ # === Q projection + (optional) gate extraction ===
511
+ q_proj_out = self.q_proj(hidden_states)
512
+ if self._has_dyn_gate_logits:
513
+ query_states, gate = torch.split(
514
+ q_proj_out,
515
+ [
516
+ self.config.num_attention_heads * self.head_dim,
517
+ self.aha_router_outputs,
518
+ ],
519
+ dim=-1,
520
+ )
521
+ else:
522
+ query_states = q_proj_out
523
+ # DUO mode: use a zero placeholder so downstream dtype/device
524
+ # checks stay unchanged. The actual gate comes from the
525
+ # per-kv-head scalar ``full_attention_heads`` below.
526
+ gate = torch.zeros(
527
+ *input_shape, self.config.num_key_value_heads,
528
+ dtype=query_states.dtype, device=query_states.device,
529
+ )
530
+
531
+ # Qwen3-style: norm then reshape then transpose
532
+ query_states = self.q_norm(query_states.view(hidden_shape)).transpose(1, 2)
533
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
534
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
535
+
536
+ cos, sin = position_embeddings
537
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
538
+
539
+ if past_key_values is not None:
540
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
541
+ key_states, value_states = past_key_values.update(
542
+ key_states, value_states, self.layer_idx, cache_kwargs
543
+ )
544
+
545
+ # === Gate computation ===
546
+ # gate shape: (B, Q, H_kv) — per-(token, kv_head) in dynamic mode,
547
+ # or per-kv_head broadcast to (B, Q, H_kv) in DUO mode.
548
+ # At inference, threshold is read from env var AHA_GATE_HARD_THRESHOLD
549
+ # so users can trade sparsity for quality without re-training.
550
+ # Default 0.5 keeps training-time behaviour. Lowering to e.g. 0.35
551
+ # lets more (token, kv_head) pairs route through global attention,
552
+ # which recovers long-CoT quality at the cost of sparsity.
553
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
554
+ if aha_mode == "duo":
555
+ # DuoAttention: alpha is per-(layer, kv_head) learnable scalar.
556
+ # gate_soft = clamp(alpha, 0, 1); gate_hard = (alpha > 0.5).
557
+ # Both broadcast to (B, Q, H_kv) for a uniform downstream API
558
+ # (so the distill / aux / blend paths do not need to branch).
559
+ qhead_alpha = getattr(self, "_aha_qhead_full_attention_heads", None)
560
+ if qhead_alpha is not None:
561
+ # Eval-only oracle diagnostic: route at Q-head granularity
562
+ # instead of KV-head/GQA-group granularity. This does not
563
+ # represent a KV-capacity-saving deployment point; it isolates
564
+ # whether GQA grouping itself is the quality bottleneck.
565
+ alpha = qhead_alpha.to(gate.dtype).clamp(0.0, 1.0)
566
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
567
+ import os as _os
568
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
569
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
570
+ gate_ste = gate_hard
571
+
572
+ else:
573
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
574
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
575
+ if self.training:
576
+ gate_hard = (alpha > 0.5).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
577
+ # Straight-through estimator through the clamped alpha so
578
+ # L1 / distill gradients flow into the scalar.
579
+ gate_ste = gate_soft
580
+ else:
581
+ import os as _os
582
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
583
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
584
+ gate_ste = gate_hard
585
+ elif aha_mode == "duo_dynamic":
586
+ # Duo + AHA-on-full-heads:
587
+ # duo_full_mask = 1 for static Duo full/retrieval heads
588
+ # dyn_gate = per-token AHA decision inside those heads
589
+ # effective_full = duo_full_mask AND dyn_gate
590
+ #
591
+ # Duo streaming heads are locked to local/streaming forever. This
592
+ # preserves Duo's KV-capacity saving: only static-full heads need a
593
+ # full KV cache. ``aha_force_gate_value=1`` therefore reproduces
594
+ # the static Duo deployment point exactly, not all-full attention.
595
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
596
+ duo_full = (alpha > 0.5).to(gate.dtype).view(1, 1, -1)
597
+ dyn_soft = torch.sigmoid(gate)
598
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
599
+ if force_gate_value is not None:
600
+ dyn_soft = torch.full_like(dyn_soft, float(force_gate_value))
601
+ gate_soft = dyn_soft * duo_full
602
+ gate_hard = gate_soft
603
+ gate_ste = gate_soft
604
+ elif self.training:
605
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
606
+ gate_soft = dyn_soft * duo_full
607
+ dyn_hard = (dyn_soft > train_thr).to(gate.dtype)
608
+ gate_hard = dyn_hard * duo_full
609
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
610
+ else:
611
+ import os as _os
612
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
613
+ gate_soft = dyn_soft * duo_full
614
+ dyn_hard = (dyn_soft > _thr).to(gate.dtype)
615
+ gate_hard = dyn_hard * duo_full
616
+ gate_ste = gate_hard
617
+ force_full = self._aha_force_full_heads_mask.to(gate.dtype).view(1, 1, -1)
618
+ if bool(force_full.any().item()):
619
+ force_full = force_full * duo_full
620
+ gate_hard = torch.maximum(gate_hard, force_full)
621
+ gate_ste = torch.maximum(gate_ste, force_full)
622
+ else:
623
+ gate_soft = torch.sigmoid(gate)
624
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
625
+ if force_gate_value is not None:
626
+ force = float(force_gate_value)
627
+ gate_soft = torch.full_like(gate_soft, force)
628
+ gate_hard = torch.full_like(gate_soft, force)
629
+ gate_ste = gate_soft
630
+ elif self.training:
631
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
632
+ gate_hard = (gate_soft > train_thr).to(gate.dtype)
633
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
634
+ else:
635
+ import os as _os
636
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
637
+ gate_hard = (gate_soft > _thr).to(gate.dtype)
638
+ gate_ste = gate_hard
639
+
640
+ # Normalize the downstream contract to effective
641
+ # token x KV-head decisions. In shared-token mode there is only one
642
+ # learned logit per token, but regularization, sparsity accounting, and
643
+ # attention mixing all see the same KV-head denominator as the
644
+ # head-granular arm.
645
+ if gate_soft.shape[-1] == 1 and self.config.num_key_value_heads != 1:
646
+ effective_shape = (*gate_soft.shape[:-1], self.config.num_key_value_heads)
647
+ gate_soft = gate_soft.expand(effective_shape)
648
+ gate_hard = gate_hard.expand(effective_shape)
649
+ gate_ste = gate_ste.expand(effective_shape)
650
+
651
+ if (
652
+ aha_mode in ("duo", "duo_dynamic")
653
+ and not self.training
654
+ and os.environ.get("AHA_DUO_PREFILL_FULL", "0") == "1"
655
+ and query_states.shape[-2] == key_states.shape[-2]
656
+ ):
657
+ # Diagnostic parity with the official DuoAttention HF eval path:
658
+ # initial prefill is full attention; streaming heads are only
659
+ # localized during subsequent decode steps.
660
+ gate_hard = torch.ones_like(gate_hard)
661
+ gate_ste = torch.ones_like(gate_ste)
662
+
663
+ if not self.training:
664
+ phase = (
665
+ "decode"
666
+ if hidden_states.shape[1] == 1 and key_states.shape[-2] > 1
667
+ else "prefill"
668
+ )
669
+ _track_aha_inference_sparsity(
670
+ gate_hard, gate_soft, self.layer_idx, phase,
671
+ getattr(
672
+ self.config,
673
+ "aha_router_granularity",
674
+ AHA_ROUTER_GRANULARITY,
675
+ ),
676
+ self.aha_router_outputs if self._has_dyn_gate_logits
677
+ else self.config.num_key_value_heads,
678
+ )
679
+
680
+ # === Attention: double_mix (global + local, blended by gate) ===
681
+ from transformers.models.qwen3.modeling_qwen3 import eager_attention_forward
682
+
683
+ attention_interface: Callable = eager_attention_forward
684
+ if self.config._attn_implementation != "eager":
685
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
686
+
687
+ # Global attention (full context)
688
+ global_attn_output, _ = attention_interface(
689
+ self, query_states, key_states, value_states,
690
+ attention_mask["global"],
691
+ dropout=0.0 if not self.training else self.attention_dropout,
692
+ scaling=self.scaling,
693
+ **kwargs,
694
+ )
695
+
696
+ if getattr(self.config, "_aha_teacher_full_fastpath", False):
697
+ attn_output = global_attn_output.reshape(*input_shape, -1).contiguous()
698
+ attn_output = self.o_proj(attn_output)
699
+ return attn_output, gate_soft, gate_hard, None
700
+
701
+ # Local / streaming branch. The local mask was pre-built by the
702
+ # model's ``forward`` either as sliding-window (legacy dynamic mode
703
+ # default) or sink+recent (duo mode, and dynamic mode when
704
+ # ``aha_local_kind="sink_recent"``). Only pass ``sliding_window`` to
705
+ # the SDPA backend when the mask is the sliding-window kind, otherwise
706
+ # the backend will double-mask and wipe out the sink positions.
707
+ local_attn_kwargs = dict(
708
+ dropout=0.0 if not self.training else self.attention_dropout,
709
+ scaling=self.scaling,
710
+ )
711
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
712
+ self.config, "aha_local_kind", "sliding_window"
713
+ )
714
+ if local_kind == "sliding_window":
715
+ local_attn_kwargs["sliding_window"] = self.aha_window_size
716
+ local_attn_output, _ = attention_interface(
717
+ self, query_states, key_states, value_states,
718
+ attention_mask["local"],
719
+ **local_attn_kwargs,
720
+ **kwargs,
721
+ )
722
+
723
+ # Blend: normal gate_ste is (B, Q, H_kv); broadcast to (B, Q, H, 1)
724
+ # by repeating each kv_head decision. The optional qhead oracle above
725
+ # already emits (B, Q, H), so it bypasses this broadcast.
726
+ if gate_ste.shape[-1] == self.config.num_attention_heads:
727
+ g_bqh = gate_ste
728
+ else:
729
+ g_bqh = gate_ste.repeat_interleave(self.num_key_value_groups, dim=-1)
730
+ attn_output = (
731
+ global_attn_output * g_bqh.unsqueeze(-1)
732
+ + local_attn_output * (1 - g_bqh.unsqueeze(-1))
733
+ )
734
+
735
+ # Per-layer attention distillation signal. Returns a scalar so the
736
+ # outer model can aggregate across layers. When gate → 1 the term
737
+ # vanishes; when gate → 0 on positions where global/local disagree
738
+ # strongly, the gradient pushes gate back toward 1.
739
+ #
740
+ # ``diff`` is ``detach``-ed so distill only trains the gate; it does
741
+ # not reshape attention outputs through the backbone. This also
742
+ # prevents the squared-difference graph from retaining activations
743
+ # for all 28 layers' global/local branches, which was the source of
744
+ # OOM when gradient checkpointing is disabled on the custom forward.
745
+ distill_per_layer: Optional[torch.Tensor] = None
746
+ if self.training and getattr(self.config, "aha_distill_weight", 0.0) > 0.0:
747
+ with torch.no_grad():
748
+ diff_sq = (global_attn_output - local_attn_output).float() ** 2
749
+ one_minus_g_sq = ((1.0 - g_bqh.float()) ** 2).unsqueeze(-1)
750
+ distill_per_layer = (one_minus_g_sq * diff_sq).mean()
751
+
752
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
753
+ attn_output = self.o_proj(attn_output)
754
+ return attn_output, gate_soft, gate_hard, distill_per_layer
755
+
756
+
757
+ class AHAQwen3DecoderLayer(GradientCheckpointingLayer):
758
+ """Decoder layer with gradient-checkpointing support.
759
+
760
+ Inheriting from ``GradientCheckpointingLayer`` lets HuggingFace Trainer's
761
+ ``gradient_checkpointing_enable`` take effect on this layer without any
762
+ explicit ``_gradient_checkpointing_func`` call in the outer forward.
763
+ """
764
+
765
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
766
+ super().__init__()
767
+ self.hidden_size = config.hidden_size
768
+ self.self_attn = AHAQwen3Attention(config=config, layer_idx=layer_idx)
769
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3MLP
770
+ self.mlp = Qwen3MLP(config)
771
+ self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
772
+ self.post_attention_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
773
+
774
+ def forward(
775
+ self,
776
+ hidden_states: torch.Tensor,
777
+ attention_mask: Optional[dict] = None,
778
+ position_ids: Optional[torch.LongTensor] = None,
779
+ past_key_values: Optional[Cache] = None,
780
+ use_cache: Optional[bool] = False,
781
+ cache_position: Optional[torch.LongTensor] = None,
782
+ position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
783
+ **kwargs,
784
+ ):
785
+ residual = hidden_states
786
+ hidden_states = self.input_layernorm(hidden_states)
787
+ hidden_states, gate_soft, gate_hard, distill_per_layer = self.self_attn(
788
+ hidden_states=hidden_states,
789
+ attention_mask=attention_mask,
790
+ position_ids=position_ids,
791
+ past_key_values=past_key_values,
792
+ use_cache=use_cache,
793
+ cache_position=cache_position,
794
+ position_embeddings=position_embeddings,
795
+ **kwargs,
796
+ )
797
+ hidden_states = residual + hidden_states
798
+
799
+ residual = hidden_states
800
+ hidden_states = self.post_attention_layernorm(hidden_states)
801
+ hidden_states = self.mlp(hidden_states)
802
+ hidden_states = residual + hidden_states
803
+ return hidden_states, gate_soft, gate_hard, distill_per_layer
804
+
805
+
806
+ @dataclass
807
+ class AHACausalLMOutputWithPast(CausalLMOutputWithPast):
808
+ """Causal LM output with AHA loss breakdown and gate density stats (training / eval with labels)."""
809
+
810
+ ce_loss: Optional[torch.FloatTensor] = None
811
+ gate_aux_loss: Optional[torch.FloatTensor] = None
812
+ distill_loss: Optional[torch.FloatTensor] = None
813
+ gate_soft_mean: Optional[torch.FloatTensor] = None
814
+ gate_hard_mean: Optional[torch.FloatTensor] = None
815
+
816
+
817
+ class AHAModelOutputWithPast(BaseModelOutputWithPast):
818
+ def __init__(self, last_hidden_state, past_key_values=None, hidden_states=None, attentions=None,
819
+ all_gate_soft=None, all_gate_hard=None, all_distill_per_layer=None):
820
+ super().__init__(last_hidden_state=last_hidden_state, past_key_values=past_key_values,
821
+ hidden_states=hidden_states, attentions=attentions)
822
+ self.all_gate_soft = all_gate_soft
823
+ self.all_gate_hard = all_gate_hard
824
+ # Tuple of per-layer distill scalars, or empty tuple when distillation is off.
825
+ self.all_distill_per_layer = all_distill_per_layer if all_distill_per_layer is not None else ()
826
+
827
+
828
+ class AHAQwen3Model(Qwen3Model):
829
+ config_class = AHAQwen3Config
830
+
831
+ def __init__(self, config: AHAQwen3Config):
832
+ from transformers import PreTrainedModel
833
+ PreTrainedModel.__init__(self, config)
834
+ self.padding_idx = config.pad_token_id
835
+ self.vocab_size = config.vocab_size
836
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
837
+ self.layers = nn.ModuleList(
838
+ [AHAQwen3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
839
+ )
840
+ self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
841
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3RotaryEmbedding
842
+ self.rotary_emb = Qwen3RotaryEmbedding(config=config)
843
+ self.gradient_checkpointing = False
844
+ self.post_init()
845
+
846
+ def forward(
847
+ self,
848
+ input_ids: Optional[torch.LongTensor] = None,
849
+ attention_mask: Optional[torch.Tensor] = None,
850
+ position_ids: Optional[torch.LongTensor] = None,
851
+ past_key_values: Optional[Cache] = None,
852
+ inputs_embeds: Optional[torch.FloatTensor] = None,
853
+ use_cache: Optional[bool] = None,
854
+ cache_position: Optional[torch.LongTensor] = None,
855
+ **kwargs,
856
+ ) -> AHAModelOutputWithPast:
857
+ if (input_ids is None) ^ (inputs_embeds is not None):
858
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
859
+ if inputs_embeds is None:
860
+ inputs_embeds = self.embed_tokens(input_ids)
861
+ if use_cache and past_key_values is None:
862
+ past_key_values = DynamicCache(config=self.config)
863
+ if cache_position is None:
864
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
865
+ cache_position = torch.arange(
866
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
867
+ )
868
+ if position_ids is None:
869
+ position_ids = cache_position.unsqueeze(0)
870
+
871
+ # Build two masks: global (full causal) and local (sliding window
872
+ # for dynamic mode, streaming sink+recent for DUO mode).
873
+ orig_sliding_window = getattr(self.config, "sliding_window", None)
874
+ self.config.sliding_window = self.config.aha_window_size
875
+ mask_kwargs = {
876
+ "config": self.config,
877
+ "input_embeds": inputs_embeds,
878
+ "attention_mask": attention_mask,
879
+ "cache_position": cache_position,
880
+ "past_key_values": past_key_values,
881
+ "position_ids": position_ids,
882
+ }
883
+ global_mask = create_causal_mask(**mask_kwargs)
884
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
885
+ # duo / duo_dynamic force sink_recent for backward compat. dynamic mode
886
+ # reads the local kind from config (default sliding_window for legacy
887
+ # Bv3 ckpts; hot-started ckpts from duo set sink_recent — see
888
+ # §9.4.13.3).
889
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
890
+ self.config, "aha_local_kind", "sliding_window"
891
+ )
892
+ if local_kind == "sink_recent":
893
+ seq_len_q = inputs_embeds.shape[1]
894
+ past_seen = past_key_values.get_seq_length() if past_key_values is not None else 0
895
+ seq_len_kv = seq_len_q + past_seen
896
+ local_mask = _build_streaming_causal_mask(
897
+ seq_len_q=seq_len_q,
898
+ seq_len_kv=seq_len_kv,
899
+ sink_size=int(getattr(self.config, "duo_sink_size", 64)),
900
+ recent_size=int(getattr(self.config, "duo_recent_size", 256)),
901
+ device=inputs_embeds.device,
902
+ dtype=inputs_embeds.dtype,
903
+ kv_offset=past_seen,
904
+ )
905
+ else:
906
+ local_mask = create_sliding_window_causal_mask(**mask_kwargs)
907
+ causal_mask_dict = {"global": global_mask, "local": local_mask}
908
+ self.config.sliding_window = orig_sliding_window
909
+
910
+ hidden_states = inputs_embeds
911
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
912
+
913
+ all_gate_soft = ()
914
+ all_gate_hard = ()
915
+ all_distill_per_layer: tuple[torch.Tensor, ...] = ()
916
+ for decoder_layer in self.layers:
917
+ hidden_states, gate_soft, gate_hard, distill_per_layer = decoder_layer(
918
+ hidden_states,
919
+ attention_mask=causal_mask_dict,
920
+ position_ids=position_ids,
921
+ past_key_values=past_key_values,
922
+ use_cache=use_cache,
923
+ cache_position=cache_position,
924
+ position_embeddings=position_embeddings,
925
+ **kwargs,
926
+ )
927
+ all_gate_soft += (gate_soft,)
928
+ all_gate_hard += (gate_hard,)
929
+ if distill_per_layer is not None:
930
+ all_distill_per_layer += (distill_per_layer,)
931
+
932
+ hidden_states = self.norm(hidden_states)
933
+ return AHAModelOutputWithPast(
934
+ last_hidden_state=hidden_states,
935
+ past_key_values=past_key_values if use_cache else None,
936
+ all_gate_soft=all_gate_soft,
937
+ all_gate_hard=all_gate_hard,
938
+ all_distill_per_layer=all_distill_per_layer,
939
+ )
940
+
941
+
942
+ class AHAQwen3ForCausalLM(Qwen3ForCausalLM):
943
+ config_class = AHAQwen3Config
944
+ _tied_weights_keys = ["lm_head.weight"]
945
+
946
+ def __init__(self, config: AHAQwen3Config):
947
+ # Skip Qwen3ForCausalLM.__init__ which creates Qwen3Model
948
+ # Go to PreTrainedModel.__init__ directly
949
+ from transformers import PreTrainedModel
950
+ PreTrainedModel.__init__(self, config)
951
+ self.model = AHAQwen3Model(config)
952
+ self.vocab_size = config.vocab_size
953
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
954
+ self.post_init()
955
+
956
+ def forward(
957
+ self,
958
+ input_ids: Optional[torch.LongTensor] = None,
959
+ attention_mask: Optional[torch.Tensor] = None,
960
+ position_ids: Optional[torch.LongTensor] = None,
961
+ past_key_values: Optional[Cache] = None,
962
+ inputs_embeds: Optional[torch.FloatTensor] = None,
963
+ labels: Optional[torch.LongTensor] = None,
964
+ use_cache: Optional[bool] = None,
965
+ cache_position: Optional[torch.LongTensor] = None,
966
+ logits_to_keep: Union[int, torch.Tensor] = 0,
967
+ **kwargs,
968
+ ) -> AHACausalLMOutputWithPast:
969
+ outputs = self.model(
970
+ input_ids=input_ids,
971
+ attention_mask=attention_mask,
972
+ position_ids=position_ids,
973
+ past_key_values=past_key_values,
974
+ inputs_embeds=inputs_embeds,
975
+ use_cache=use_cache,
976
+ cache_position=cache_position,
977
+ **kwargs,
978
+ )
979
+ hidden_states = outputs.last_hidden_state
980
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
981
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
982
+
983
+ loss = None
984
+ ce_loss = None
985
+ gate_aux_loss = None
986
+ distill_loss = None
987
+ gate_soft_mean = None
988
+ gate_hard_mean = None
989
+
990
+ if labels is not None:
991
+ from torch.nn import CrossEntropyLoss
992
+ shift_logits = logits[..., :-1, :].contiguous()
993
+ shift_labels = labels[..., 1:].contiguous()
994
+ loss_fct = CrossEntropyLoss()
995
+ ce_loss = loss_fct(shift_logits.view(-1, self.vocab_size), shift_labels.view(-1))
996
+
997
+ all_gate_soft = outputs.all_gate_soft
998
+ all_gate_hard = outputs.all_gate_hard
999
+ if labels is not None and len(all_gate_soft) > 0 and len(all_gate_hard) > 0:
1000
+ gate_soft_stack = torch.stack(
1001
+ [g.to(hidden_states.device) for g in all_gate_soft], dim=-1
1002
+ ).float()
1003
+ gate_hard_stack = torch.stack(
1004
+ [g.to(hidden_states.device).float() for g in all_gate_hard], dim=-1
1005
+ )
1006
+ shift_gate_soft = gate_soft_stack[:, :-1, :].contiguous()
1007
+ shift_gate_hard = gate_hard_stack[:, :-1, :].contiguous()
1008
+ # Match the CE/PPL population exactly. The old unmasked mean
1009
+ # counted padded sequence positions as real gate decisions, which
1010
+ # made the training sparsity curve disagree with inference-time
1011
+ # sparsity on variable-length batches.
1012
+ valid_gate = shift_labels.ne(-100)
1013
+ if attention_mask is not None:
1014
+ valid_gate = valid_gate & attention_mask[:, 1:].to(torch.bool)
1015
+ if bool(valid_gate.any()):
1016
+ valid_gate = valid_gate[..., None, None].to(shift_gate_soft.dtype)
1017
+ normalizer = (
1018
+ valid_gate.sum()
1019
+ * shift_gate_soft.shape[-2]
1020
+ * shift_gate_soft.shape[-1]
1021
+ )
1022
+ gate_soft_mean = (shift_gate_soft * valid_gate).sum() / normalizer
1023
+ gate_hard_mean = (shift_gate_hard * valid_gate).sum() / normalizer
1024
+ else:
1025
+ gate_soft_mean = shift_gate_soft.mean()
1026
+ gate_hard_mean = shift_gate_hard.mean()
1027
+
1028
+ # Per-layer distillation loss: mean over layers of
1029
+ # ``mean(((1-g)·(global-local))**2)``. Populated only when training
1030
+ # with ``config.aha_distill_weight > 0`` (see AHAQwen3Attention).
1031
+ all_distill = outputs.all_distill_per_layer
1032
+ if self.training and len(all_distill) > 0:
1033
+ distill_loss = torch.stack(list(all_distill)).mean()
1034
+
1035
+ # === Loss assembly ===
1036
+ # Eval path: standard LM loss for compatibility with HF metrics.
1037
+ # Train path: weighted sum of (ce, gate_aux, distill) so callers can
1038
+ # zero any term by setting its weight to 0 (e.g. ``aha_ce_weight=0``
1039
+ # to train the gate purely from ``aux + distill``).
1040
+ if not self.training:
1041
+ loss = ce_loss
1042
+ else:
1043
+ ce_weight = float(getattr(self.config, "aha_ce_weight", 1.0))
1044
+ distill_weight = float(getattr(self.config, "aha_distill_weight", 0.0))
1045
+ terms: list[torch.Tensor] = []
1046
+ if ce_loss is not None and ce_weight > 0.0:
1047
+ terms.append(ce_weight * ce_loss)
1048
+ if gate_soft_mean is not None:
1049
+ reg_weight = float(getattr(self.config, "aha_reg_weight", -1.0))
1050
+ if reg_weight >= 0.0:
1051
+ # Duo-style direct sparsity term:
1052
+ # alpha/gate near 1 means full/global attention, so penalize
1053
+ # mean gate usage directly.
1054
+ gate_aux_loss = reg_weight * gate_soft_mean
1055
+ else:
1056
+ # Legacy hinge aux: penalise only the excess above the target
1057
+ # ceiling. Below ``τ`` the gradient is zero, so CE and
1058
+ # distill freely shape where the gate opens without an
1059
+ # unbounded downward push from aux. See AHAQwen3Config
1060
+ # for the rationale and the collapse that motivated it.
1061
+ gate_target = float(getattr(self.config, "aha_gate_target", 1.0))
1062
+ excess = torch.clamp(gate_soft_mean - gate_target, min=0.0)
1063
+ gate_aux_loss = self.config.aha_lambda * excess
1064
+ terms.append(gate_aux_loss)
1065
+ if distill_loss is not None and distill_weight > 0.0:
1066
+ terms.append(distill_weight * distill_loss)
1067
+ if terms:
1068
+ loss = terms[0]
1069
+ for t in terms[1:]:
1070
+ loss = loss + t
1071
+
1072
+ return AHACausalLMOutputWithPast(
1073
+ loss=loss,
1074
+ logits=logits,
1075
+ past_key_values=outputs.past_key_values,
1076
+ ce_loss=ce_loss,
1077
+ gate_aux_loss=gate_aux_loss,
1078
+ distill_loss=distill_loss,
1079
+ gate_soft_mean=gate_soft_mean,
1080
+ gate_hard_mean=gate_hard_mean,
1081
+ )
1082
+
1083
+ @classmethod
1084
+ def from_pretrained_qwen3(cls, qwen3_path: str, aha_window_size: int = AHA_WINDOW_SIZE,
1085
+ aha_lambda: float = AHA_LAMBDA,
1086
+ aha_distill_weight: float = AHA_DISTILL_WEIGHT,
1087
+ aha_ce_weight: float = AHA_CE_WEIGHT,
1088
+ aha_gate_target: float = AHA_GATE_TARGET,
1089
+ aha_reg_weight: float = -1.0,
1090
+ aha_force_gate_value=None,
1091
+ aha_mode: str = AHA_MODE,
1092
+ duo_sink_size: int = DUO_SINK_SIZE,
1093
+ duo_recent_size: int = DUO_RECENT_SIZE,
1094
+ duo_alpha_init: float = DUO_ALPHA_INIT,
1095
+ aha_local_kind: str = AHA_LOCAL_KIND,
1096
+ aha_router_granularity: str = AHA_ROUTER_GRANULARITY,
1097
+ **kwargs):
1098
+ """Load a pretrained Qwen3 model and convert to AHA-Qwen3.
1099
+
1100
+ Initializes the gate weights in q_proj randomly (the extra num_heads outputs).
1101
+ All other weights are copied from the pretrained model.
1102
+ """
1103
+ # Load original Qwen3
1104
+ original = Qwen3ForCausalLM.from_pretrained(qwen3_path, **kwargs)
1105
+ original_config = original.config
1106
+
1107
+ # Create AHA config
1108
+ config_dict = original_config.to_dict()
1109
+ config_dict["aha_window_size"] = aha_window_size
1110
+ config_dict["aha_lambda"] = aha_lambda
1111
+ config_dict["aha_distill_weight"] = aha_distill_weight
1112
+ config_dict["aha_ce_weight"] = aha_ce_weight
1113
+ config_dict["aha_gate_target"] = aha_gate_target
1114
+ config_dict["aha_reg_weight"] = aha_reg_weight
1115
+ config_dict["aha_force_gate_value"] = aha_force_gate_value
1116
+ config_dict["aha_mode"] = aha_mode
1117
+ config_dict["duo_sink_size"] = duo_sink_size
1118
+ config_dict["duo_recent_size"] = duo_recent_size
1119
+ config_dict["duo_alpha_init"] = duo_alpha_init
1120
+ config_dict["aha_local_kind"] = aha_local_kind
1121
+ config_dict["aha_router_granularity"] = aha_router_granularity
1122
+ config_dict["model_type"] = "aha_qwen3"
1123
+ aha_config = AHAQwen3Config(**config_dict)
1124
+ aha_config._attn_implementation = kwargs.get("attn_implementation", "sdpa")
1125
+
1126
+ # Create AHA model
1127
+ aha_model = cls(aha_config)
1128
+
1129
+ # Copy weights
1130
+ # embed_tokens, lm_head, norm
1131
+ aha_model.model.embed_tokens.load_state_dict(original.model.embed_tokens.state_dict())
1132
+ aha_model.lm_head.load_state_dict(original.lm_head.state_dict())
1133
+ aha_model.model.norm.load_state_dict(original.model.norm.state_dict())
1134
+ aha_model.model.rotary_emb.load_state_dict(original.model.rotary_emb.state_dict())
1135
+
1136
+ # Copy per-layer weights
1137
+ num_heads = original_config.num_attention_heads
1138
+ head_dim = aha_config.head_dim
1139
+
1140
+ for i in range(original_config.num_hidden_layers):
1141
+ orig_layer = original.model.layers[i]
1142
+ aha_layer = aha_model.model.layers[i]
1143
+
1144
+ # MLP + norms: direct copy
1145
+ aha_layer.mlp.load_state_dict(orig_layer.mlp.state_dict())
1146
+ aha_layer.input_layernorm.load_state_dict(orig_layer.input_layernorm.state_dict())
1147
+ aha_layer.post_attention_layernorm.load_state_dict(orig_layer.post_attention_layernorm.state_dict())
1148
+
1149
+ # Attention: k_proj, v_proj, o_proj, q_norm, k_norm — direct copy
1150
+ aha_attn = aha_layer.self_attn
1151
+ orig_attn = orig_layer.self_attn
1152
+ aha_attn.k_proj.load_state_dict(orig_attn.k_proj.state_dict())
1153
+ aha_attn.v_proj.load_state_dict(orig_attn.v_proj.state_dict())
1154
+ aha_attn.o_proj.load_state_dict(orig_attn.o_proj.state_dict())
1155
+ aha_attn.q_norm.load_state_dict(orig_attn.q_norm.state_dict())
1156
+ aha_attn.k_norm.load_state_dict(orig_attn.k_norm.state_dict())
1157
+
1158
+ # q_proj:
1159
+ # - dynamic / duo_dynamic mode: copy original Q rows, init extra
1160
+ # gate rows random (~0.5).
1161
+ # - duo mode: q_proj is identical shape to base Qwen3 (no gate
1162
+ # rows), so direct load_state_dict works.
1163
+ orig_q_weight = orig_attn.q_proj.weight.data # (num_heads*head_dim, hidden_size)
1164
+ aha_q_weight = aha_attn.q_proj.weight.data
1165
+ if aha_attn._has_dyn_gate_logits:
1166
+ aha_q_weight[:num_heads * head_dim, :] = orig_q_weight
1167
+ nn.init.normal_(aha_q_weight[num_heads * head_dim:, :], mean=0.0, std=0.01)
1168
+ if orig_attn.q_proj.bias is not None and aha_attn.q_proj.bias is not None:
1169
+ aha_attn.q_proj.bias.data[:num_heads * head_dim] = orig_attn.q_proj.bias.data
1170
+ nn.init.zeros_(aha_attn.q_proj.bias.data[num_heads * head_dim:])
1171
+ else:
1172
+ aha_attn.q_proj.load_state_dict(orig_attn.q_proj.state_dict())
1173
+
1174
+ del original
1175
+ return aha_model
1176
+
1177
+ @classmethod
1178
+ def from_pretrained_aha(cls, checkpoint_path: str, **kwargs):
1179
+ """Load an AHA-Qwen3 checkpoint saved by `trainer.save_model`.
1180
+
1181
+ Supports both single-file (``model.safetensors``) and multi-shard
1182
+ (``model.safetensors.index.json`` + ``model-*-of-*.safetensors``)
1183
+ layouts. The latter is automatically used by HF when a checkpoint
1184
+ exceeds the single-file size threshold (e.g. Qwen3-1.7B+).
1185
+ """
1186
+ config = AHAQwen3Config.from_pretrained(checkpoint_path)
1187
+ config._attn_implementation = kwargs.get(
1188
+ "attn_implementation",
1189
+ getattr(config, "_attn_implementation", "sdpa"),
1190
+ )
1191
+
1192
+ model = cls(config)
1193
+
1194
+ single_path = os.path.join(checkpoint_path, "model.safetensors")
1195
+ index_path = os.path.join(checkpoint_path, "model.safetensors.index.json")
1196
+ if os.path.exists(single_path):
1197
+ state_dict = load_file(single_path)
1198
+ elif os.path.exists(index_path):
1199
+ with open(index_path) as f:
1200
+ shard_index = json.load(f)
1201
+ shard_files = sorted(set(shard_index["weight_map"].values()))
1202
+ state_dict = {}
1203
+ for shard in shard_files:
1204
+ state_dict.update(load_file(os.path.join(checkpoint_path, shard)))
1205
+ else:
1206
+ raise FileNotFoundError(
1207
+ f"No safetensors file or index found in {checkpoint_path}. "
1208
+ f"Expected either model.safetensors or model.safetensors.index.json."
1209
+ )
1210
+ missing_keys, unexpected_keys = model.load_state_dict(state_dict, strict=False)
1211
+ allowed_missing = {"lm_head.weight"} if getattr(config, "tie_word_embeddings", False) else set()
1212
+ extra_missing = set(missing_keys) - allowed_missing
1213
+ if extra_missing or unexpected_keys:
1214
+ raise RuntimeError(
1215
+ f"Unexpected checkpoint mismatch. missing={sorted(extra_missing)}, "
1216
+ f"unexpected={sorted(unexpected_keys)}"
1217
+ )
1218
+ if "lm_head.weight" in missing_keys:
1219
+ model.tie_weights()
1220
+
1221
+ _apply_low_alpha_force_full_heads_from_env(model)
1222
+
1223
+ torch_dtype = kwargs.get("torch_dtype")
1224
+ if torch_dtype is not None:
1225
+ model = model.to(dtype=torch_dtype)
1226
+ return model
outputs/l2a_style/stage1/checkpoint-100/special_tokens_map.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>",
5
+ "<|object_ref_start|>",
6
+ "<|object_ref_end|>",
7
+ "<|box_start|>",
8
+ "<|box_end|>",
9
+ "<|quad_start|>",
10
+ "<|quad_end|>",
11
+ "<|vision_start|>",
12
+ "<|vision_end|>",
13
+ "<|vision_pad|>",
14
+ "<|image_pad|>",
15
+ "<|video_pad|>"
16
+ ],
17
+ "eos_token": {
18
+ "content": "<|im_end|>",
19
+ "lstrip": false,
20
+ "normalized": false,
21
+ "rstrip": false,
22
+ "single_word": false
23
+ },
24
+ "pad_token": {
25
+ "content": "<|endoftext|>",
26
+ "lstrip": false,
27
+ "normalized": false,
28
+ "rstrip": false,
29
+ "single_word": false
30
+ }
31
+ }
outputs/l2a_style/stage1/checkpoint-100/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
3
+ size 11422654
outputs/l2a_style/stage1/checkpoint-100/tokenizer_config.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>"
228
+ ],
229
+ "bos_token": null,
230
+ "clean_up_tokenization_spaces": false,
231
+ "eos_token": "<|im_end|>",
232
+ "errors": "replace",
233
+ "extra_special_tokens": {},
234
+ "model_max_length": 131072,
235
+ "pad_token": "<|endoftext|>",
236
+ "split_special_tokens": false,
237
+ "tokenizer_class": "Qwen2Tokenizer",
238
+ "unk_token": null
239
+ }
outputs/l2a_style/stage1/checkpoint-100/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/stage1/checkpoint-200/added_tokens.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|box_end|>": 151649,
9
+ "<|box_start|>": 151648,
10
+ "<|endoftext|>": 151643,
11
+ "<|file_sep|>": 151664,
12
+ "<|fim_middle|>": 151660,
13
+ "<|fim_pad|>": 151662,
14
+ "<|fim_prefix|>": 151659,
15
+ "<|fim_suffix|>": 151661,
16
+ "<|im_end|>": 151645,
17
+ "<|im_start|>": 151644,
18
+ "<|image_pad|>": 151655,
19
+ "<|object_ref_end|>": 151647,
20
+ "<|object_ref_start|>": 151646,
21
+ "<|quad_end|>": 151651,
22
+ "<|quad_start|>": 151650,
23
+ "<|repo_name|>": 151663,
24
+ "<|video_pad|>": 151656,
25
+ "<|vision_end|>": 151653,
26
+ "<|vision_pad|>": 151654,
27
+ "<|vision_start|>": 151652
28
+ }
outputs/l2a_style/stage1/checkpoint-200/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 %}
outputs/l2a_style/stage1/checkpoint-200/config.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_ce_weight": 0.0,
3
+ "aha_distill_weight": 0.0,
4
+ "aha_force_gate_value": null,
5
+ "aha_gate_init_full_prob": 0.9,
6
+ "aha_gate_target": 0.7,
7
+ "aha_hotstart_source": "/workspace",
8
+ "aha_lambda": 0.0003,
9
+ "aha_local_kind": "sink_recent",
10
+ "aha_mode": "dynamic",
11
+ "aha_reg_weight": -1.0,
12
+ "aha_router_granularity": "token",
13
+ "aha_window_size": 128,
14
+ "architectures": [
15
+ "AHAQwen3ForCausalLM"
16
+ ],
17
+ "attention_bias": true,
18
+ "attention_dropout": 0.0,
19
+ "auto_map": {
20
+ "AutoConfig": "modeling_aha_qwen3.AHAQwen3Config",
21
+ "AutoModelForCausalLM": "modeling_aha_qwen3.AHAQwen3ForCausalLM"
22
+ },
23
+ "bos_token_id": 151643,
24
+ "duo_alpha_init": 1.0,
25
+ "duo_recent_size": 256,
26
+ "duo_sink_size": 64,
27
+ "eos_token_id": 151645,
28
+ "head_dim": 128,
29
+ "hidden_act": "silu",
30
+ "hidden_size": 2048,
31
+ "initializer_range": 0.02,
32
+ "intermediate_size": 6144,
33
+ "layer_types": [
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
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention",
57
+ "full_attention",
58
+ "full_attention",
59
+ "full_attention",
60
+ "full_attention",
61
+ "full_attention"
62
+ ],
63
+ "max_position_embeddings": 40960,
64
+ "max_window_layers": 28,
65
+ "model_type": "aha_qwen3",
66
+ "num_attention_heads": 16,
67
+ "num_hidden_layers": 28,
68
+ "num_key_value_heads": 8,
69
+ "rms_norm_eps": 1e-06,
70
+ "rope_scaling": null,
71
+ "rope_theta": 1000000,
72
+ "sliding_window": null,
73
+ "tie_word_embeddings": false,
74
+ "torch_dtype": "bfloat16",
75
+ "transformers_version": "4.54.0",
76
+ "use_cache": false,
77
+ "use_sliding_window": false,
78
+ "vocab_size": 151936
79
+ }
outputs/l2a_style/stage1/checkpoint-200/dynamic_duo_state.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 200,
3
+ "router_granularity": "token",
4
+ "native_gate_rows_per_layer": 1,
5
+ "effective_sparsity_denominator": "token x KV-head x layer",
6
+ "gate_soft_mean": 0.6480492353439331,
7
+ "gate_soft_std": 0.20559526979923248,
8
+ "gate_hard_mean": 0.7422441840171814,
9
+ "gate_min": 0.00159454345703125,
10
+ "gate_max": 0.9375,
11
+ "gate_weight_l2": 0.5990009903907776,
12
+ "gate_weight_abs_mean": 0.0018714622128754854,
13
+ "gate_bias_mean": 2.203125,
14
+ "gate_bias_std": 0.0,
15
+ "gate_bias_alpha_mean": 0.9005298018455505,
16
+ "gate_bias_alpha_gt05": 1.0
17
+ }
outputs/l2a_style/stage1/checkpoint-200/dynamic_duo_train_args.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_checkpoint": "/workspace/outputs/l2a_style/hotstart",
3
+ "model_path": "/workspace",
4
+ "output_dir": "/workspace/outputs/l2a_style/stage1",
5
+ "am_dataset_path": "/workspace/recipe/data/am_distilled_long_mix",
6
+ "am_dataset_split": "train",
7
+ "data_source": "am_distilled",
8
+ "am_label_mode": "full",
9
+ "longbench_tasks": [
10
+ "passage_retrieval_en",
11
+ "multifieldqa_en",
12
+ "qasper",
13
+ "2wikimqa"
14
+ ],
15
+ "longbench_samples_per_task": 30,
16
+ "longbench_cache_dir": "/workspace/AHA/AHA-Qwen3/data/longbench_cache",
17
+ "max_length": 8192,
18
+ "num_steps": 300,
19
+ "warmup_ratio": 0.1,
20
+ "lr": 3e-05,
21
+ "reg_weight": 0.1,
22
+ "reg_head_weight_mode": "uniform",
23
+ "reg_head_weight_power": 1.0,
24
+ "ce_weight": 0.0,
25
+ "distill_tail_frac": 0.0,
26
+ "distill_tail_weight": 0.0,
27
+ "batch_size": 1,
28
+ "grad_accum": 1,
29
+ "save_steps": 100,
30
+ "log_steps": 10,
31
+ "seed": 42,
32
+ "dtype": "bfloat16",
33
+ "attn_impl": "sdpa",
34
+ "aha_local_kind": "sink_recent",
35
+ "router_granularity": "token"
36
+ }
outputs/l2a_style/stage1/checkpoint-200/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 151643,
4
+ "eos_token_id": 151645,
5
+ "transformers_version": "4.54.0",
6
+ "use_cache": false
7
+ }
outputs/l2a_style/stage1/checkpoint-200/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/stage1/checkpoint-200/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb90926ddbcecdee7c766a08bdb06b8bf61c4ee60334896b12e6868d57803ce
3
+ size 4063986840
outputs/l2a_style/stage1/checkpoint-200/modeling_aha_qwen3.py ADDED
@@ -0,0 +1,1226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ AHA-Qwen3: All-or-Here Attention applied to Qwen3.
3
+
4
+ Minimal modification to standard Qwen3 attention: adds a binary gate that
5
+ dynamically toggles between full attention and local attention. The persisted
6
+ ``aha_router_granularity`` config selects either one gate per (token, KV-head)
7
+ or one gate per token shared by every head in the layer.
8
+
9
+ Based on: transformers.models.qwen3.modeling_qwen3 (Qwen3Attention)
10
+ Changes:
11
+ 1. q_proj outputs either one extra gate value (``token``) or
12
+ ``num_key_value_heads`` values (``token_kv_head``). The shared-token
13
+ value is broadcast to all KV/query heads before attention mixing.
14
+ 2. Gate: sigmoid → hard threshold → STE for training
15
+ 3. Double-mix: global attention + local window attention, blended by
16
+ gate (broadcast across the GQA group)
17
+ 4. Auxiliary loss: λ * mean(gate_soft) to encourage sparsity
18
+ """
19
+
20
+ import atexit
21
+ import json
22
+ import os
23
+ from dataclasses import dataclass
24
+ from pathlib import Path
25
+ from typing import Callable, Optional, Union
26
+
27
+ import torch
28
+ import torch.nn as nn
29
+ from safetensors.torch import load_file
30
+
31
+ from transformers import Qwen3Config, Qwen3ForCausalLM
32
+ from transformers.modeling_layers import GradientCheckpointingLayer
33
+ from transformers.models.qwen3.modeling_qwen3 import (
34
+ Qwen3Attention,
35
+ Qwen3DecoderLayer,
36
+ Qwen3Model,
37
+ Qwen3RMSNorm,
38
+ apply_rotary_pos_emb,
39
+ )
40
+ from transformers.cache_utils import Cache, DynamicCache
41
+ from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
42
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
43
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
44
+ from transformers.processing_utils import Unpack
45
+ from transformers.utils.generic import TransformersKwargs
46
+
47
+ # AHA constants
48
+ AHA_WINDOW_SIZE = 1024
49
+ AHA_LAMBDA = 3e-4
50
+ AHA_DISTILL_WEIGHT = 0.0
51
+ AHA_CE_WEIGHT = 1.0
52
+ # Gate-density ceiling (hinge target). The aux loss only fires when
53
+ # ``mean(gate_soft) > AHA_GATE_TARGET``, so sparsity cannot drop
54
+ # below ``1 - AHA_GATE_TARGET`` in steady state regardless of how
55
+ # large ``aha_lambda`` is. Setting this to 1.0 recovers the legacy
56
+ # behaviour (unconditional downward pressure).
57
+ AHA_GATE_TARGET = 1.0
58
+
59
+ # DuoAttention baseline knobs. When ``aha_mode == "duo"`` the per-(token,
60
+ # kv_head) gate is replaced by a per-(layer, kv_head) learnable scalar
61
+ # (``duo_alpha``, 224 scalars total for Qwen3-0.6B), and the "local"
62
+ # branch becomes a streaming mask with ``duo_sink_size`` attention sinks
63
+ # plus a recent window of ``duo_recent_size``. This is a strict reduction
64
+ # of the dynamic AHA gate and reproduces the DuoAttention paper's head
65
+ # classifier (Xiao et al. 2024) on the Qwen3 backbone.
66
+ AHA_MODE = "dynamic"
67
+ DUO_SINK_SIZE = 64
68
+ DUO_RECENT_SIZE = 256
69
+ DUO_ALPHA_INIT = 1.0
70
+ # Local-branch mask kind for the dual-branch attention (global vs local).
71
+ # - "sliding_window": last ``aha_window_size`` tokens, no attention sinks.
72
+ # Historical default for ``aha_mode="dynamic"`` (Bv3 was trained with this).
73
+ # - "sink_recent": first ``duo_sink_size`` tokens + last ``duo_recent_size``
74
+ # tokens, matching the DuoAttention paper. Historical hardcoded default for
75
+ # ``aha_mode="duo"`` (still forced for duo regardless of this field).
76
+ # § 9.4.13.3 found that hot-starting a dynamic-mode ckpt from a duo ckpt with
77
+ # sliding_window local loses ~20pp LongBench retention vs the duo source,
78
+ # almost entirely from passage_retrieval_en (sinks are critical for long
79
+ # passage retrieval). Setting this to "sink_recent" for hot-started dynamic
80
+ # ckpts closes that architectural gap.
81
+ AHA_LOCAL_KIND = "sliding_window"
82
+ AHA_ROUTER_GRANULARITY = "token_kv_head"
83
+
84
+
85
+ def aha_router_output_size(config: Qwen3Config) -> int:
86
+ """Return the number of native dynamic-router logits per token."""
87
+
88
+ granularity = getattr(config, "aha_router_granularity", AHA_ROUTER_GRANULARITY)
89
+ if granularity == "token":
90
+ return 1
91
+ if granularity == "token_kv_head":
92
+ return int(config.num_key_value_heads)
93
+ raise ValueError(f"Unsupported aha_router_granularity={granularity!r}")
94
+
95
+
96
+ class _AHAInferenceSparsityTracker:
97
+ """Accumulate exact inference hard-route counts without changing outputs."""
98
+
99
+ def __init__(self) -> None:
100
+ # Keep counters as device scalars until process exit. Calling .item()
101
+ # once per layer and decode token serialized CUDA and made the
102
+ # diagnostic tracker materially slow generation.
103
+ self.sparse_decisions: dict[int, torch.Tensor] = {}
104
+ self.total_decisions: dict[int, int] = {}
105
+ self.phase_sparse_decisions: dict[str, torch.Tensor] = {}
106
+ self.phase_total_decisions: dict[str, int] = {}
107
+ raw_thresholds = os.environ.get("AHA_COUNTERFACTUAL_THRESHOLDS", "")
108
+ self.counterfactual_thresholds = tuple(
109
+ sorted({float(value) for value in raw_thresholds.split(",") if value.strip()})
110
+ )
111
+ if any(value < 0.0 or value > 1.0 for value in self.counterfactual_thresholds):
112
+ raise ValueError("AHA_COUNTERFACTUAL_THRESHOLDS values must be in [0, 1]")
113
+ self.counterfactual_sparse: dict[float, torch.Tensor] = {}
114
+ self.router_granularity: Optional[str] = None
115
+ self.native_router_decisions = 0
116
+ atexit.register(self.write_stats)
117
+
118
+ def update(
119
+ self,
120
+ gate_hard: torch.Tensor,
121
+ gate_soft: torch.Tensor,
122
+ layer_idx: int,
123
+ phase: str,
124
+ router_granularity: str,
125
+ native_router_width: int,
126
+ ) -> None:
127
+ if self.router_granularity is None:
128
+ self.router_granularity = router_granularity
129
+ elif self.router_granularity != router_granularity:
130
+ raise RuntimeError(
131
+ "AHA sparsity tracker received mixed router granularities: "
132
+ f"{self.router_granularity!r} and {router_granularity!r}"
133
+ )
134
+ sparse = torch.count_nonzero(gate_hard == 0).to(torch.int64)
135
+ total = int(gate_hard.numel())
136
+ effective_width = int(gate_hard.shape[-1])
137
+ self.native_router_decisions += total // effective_width * native_router_width
138
+ if layer_idx not in self.sparse_decisions:
139
+ self.sparse_decisions[layer_idx] = sparse
140
+ else:
141
+ self.sparse_decisions[layer_idx].add_(sparse)
142
+ self.total_decisions[layer_idx] = self.total_decisions.get(layer_idx, 0) + total
143
+ if phase not in self.phase_sparse_decisions:
144
+ self.phase_sparse_decisions[phase] = sparse.clone()
145
+ else:
146
+ self.phase_sparse_decisions[phase].add_(sparse)
147
+ self.phase_total_decisions[phase] = self.phase_total_decisions.get(phase, 0) + total
148
+ if self.counterfactual_thresholds:
149
+ flat = gate_soft.detach().reshape(-1, 1)
150
+ thresholds = flat.new_tensor(self.counterfactual_thresholds).reshape(1, -1)
151
+ counts = (flat <= thresholds).sum(dim=0, dtype=torch.int64)
152
+ for threshold, count in zip(self.counterfactual_thresholds, counts.unbind()):
153
+ if threshold not in self.counterfactual_sparse:
154
+ self.counterfactual_sparse[threshold] = count
155
+ else:
156
+ self.counterfactual_sparse[threshold].add_(count)
157
+
158
+ def write_stats(self) -> None:
159
+ output = os.environ.get("AHA_SPARSITY_STATS_PATH")
160
+ if not output:
161
+ return
162
+ sparse_by_layer = {
163
+ layer_idx: int(value.item())
164
+ for layer_idx, value in self.sparse_decisions.items()
165
+ }
166
+ sparse = sum(sparse_by_layer.values())
167
+ total = sum(self.total_decisions.values())
168
+ per_layer = {}
169
+ for layer_idx in sorted(self.total_decisions):
170
+ layer_sparse = sparse_by_layer.get(layer_idx, 0)
171
+ layer_total = self.total_decisions[layer_idx]
172
+ per_layer[str(layer_idx)] = {
173
+ "sparse_decisions": layer_sparse,
174
+ "total_decisions": layer_total,
175
+ "sparsity": layer_sparse / layer_total if layer_total else None,
176
+ }
177
+ payload = {
178
+ "definition": (
179
+ "hard AHA gate zeros / all token x KV-head x layer decisions, "
180
+ "token-weighted over prefill and decode"
181
+ ),
182
+ "router_granularity": self.router_granularity,
183
+ "native_router_decisions": self.native_router_decisions,
184
+ "effective_router_decisions": total,
185
+ "native_router_note": (
186
+ "Native decisions count learned gate logits before broadcast; effective "
187
+ "decisions always use token x KV-head x layer for matched sparsity."
188
+ ),
189
+ "sparse_decisions": sparse,
190
+ "total_decisions": total,
191
+ "sparsity": sparse / total if total else None,
192
+ "full_attention_usage": 1.0 - sparse / total if total else None,
193
+ "per_layer": per_layer,
194
+ "by_phase": {
195
+ phase: {
196
+ "sparse_decisions": int(self.phase_sparse_decisions[phase].item()),
197
+ "total_decisions": phase_total,
198
+ "sparsity": int(self.phase_sparse_decisions[phase].item()) / phase_total
199
+ if phase_total else None,
200
+ "full_attention_usage": 1.0
201
+ - int(self.phase_sparse_decisions[phase].item()) / phase_total
202
+ if phase_total else None,
203
+ }
204
+ for phase, phase_total in sorted(self.phase_total_decisions.items())
205
+ },
206
+ }
207
+ if self.counterfactual_thresholds:
208
+ payload["counterfactual_sparsity_by_threshold"] = {
209
+ f"{threshold:.6g}": {
210
+ "sparse_decisions": int(self.counterfactual_sparse[threshold].item()),
211
+ "total_decisions": total,
212
+ "sparsity": int(self.counterfactual_sparse[threshold].item()) / total
213
+ if total else None,
214
+ }
215
+ for threshold in self.counterfactual_thresholds
216
+ }
217
+ payload["counterfactual_note"] = (
218
+ "Routing counts only; benchmark quality must be measured in a separate "
219
+ "run with AHA_GATE_HARD_THRESHOLD set to the selected threshold."
220
+ )
221
+ path = Path(output)
222
+ path.parent.mkdir(parents=True, exist_ok=True)
223
+ path.write_text(json.dumps(payload, indent=2) + "\n")
224
+
225
+
226
+ _AHA_INFERENCE_SPARSITY_TRACKER: Optional[_AHAInferenceSparsityTracker] = None
227
+
228
+
229
+ def _track_aha_inference_sparsity(
230
+ gate_hard: torch.Tensor,
231
+ gate_soft: torch.Tensor,
232
+ layer_idx: int,
233
+ phase: str,
234
+ router_granularity: str,
235
+ native_router_width: int,
236
+ ) -> None:
237
+ global _AHA_INFERENCE_SPARSITY_TRACKER
238
+ if not os.environ.get("AHA_SPARSITY_STATS_PATH"):
239
+ return
240
+ if _AHA_INFERENCE_SPARSITY_TRACKER is None:
241
+ _AHA_INFERENCE_SPARSITY_TRACKER = _AHAInferenceSparsityTracker()
242
+ _AHA_INFERENCE_SPARSITY_TRACKER.update(
243
+ gate_hard.detach(), gate_soft.detach(), layer_idx, phase,
244
+ router_granularity, native_router_width,
245
+ )
246
+
247
+
248
+ def _get_optional_nonnegative_int_env(name: str) -> Optional[int]:
249
+ raw = os.environ.get(name, "").strip()
250
+ if not raw:
251
+ return None
252
+ value = int(raw)
253
+ if value < 0:
254
+ raise ValueError(f"{name} must be non-negative, got {value}")
255
+ return value
256
+
257
+
258
+ def _apply_low_alpha_force_full_heads_from_env(model: nn.Module) -> None:
259
+ """Auto-protect the weakest static-full heads in duo_dynamic mode.
260
+
261
+ ``AHA_FORCE_LOW_ALPHA_FULL_HEADS=K`` selects the K smallest
262
+ ``full_attention_heads`` values among heads that static Duo still kept
263
+ full (alpha > 0.5). These are the fragile boundary full heads: raising the
264
+ dynamic threshold can close them, but static Duo's alpha says they should
265
+ not be fully discarded. The selected heads are OR-ed with any manually
266
+ supplied ``AHA_FORCE_FULL_HEADS`` mask.
267
+ """
268
+ k = _get_optional_nonnegative_int_env("AHA_FORCE_LOW_ALPHA_FULL_HEADS")
269
+ if not k:
270
+ return
271
+ if getattr(model.config, "aha_mode", "dynamic") != "duo_dynamic":
272
+ return
273
+
274
+ candidates: list[tuple[float, int, int]] = []
275
+ with torch.no_grad():
276
+ for layer_idx, layer in enumerate(model.model.layers):
277
+ attn = layer.self_attn
278
+ alpha = getattr(attn, "full_attention_heads", None)
279
+ if alpha is None:
280
+ continue
281
+ for head_idx, value in enumerate(alpha.detach().float().cpu().tolist()):
282
+ if value > 0.5:
283
+ candidates.append((float(value), layer_idx, head_idx))
284
+ selected = sorted(candidates)[: min(k, len(candidates))]
285
+ if not selected:
286
+ return
287
+
288
+ selected_by_layer: dict[int, list[int]] = {}
289
+ for _, layer_idx, head_idx in selected:
290
+ selected_by_layer.setdefault(layer_idx, []).append(head_idx)
291
+ with torch.no_grad():
292
+ for layer_idx, heads in selected_by_layer.items():
293
+ mask = model.model.layers[layer_idx].self_attn._aha_force_full_heads_mask
294
+ for head_idx in heads:
295
+ mask[head_idx] = True
296
+ model.config.aha_force_low_alpha_full_heads = int(k)
297
+ formatted = ",".join(f"{layer}:{head}" for _, layer, head in selected)
298
+ print(
299
+ f"[AHA] AHA_FORCE_LOW_ALPHA_FULL_HEADS={k} selected {len(selected)} "
300
+ f"duo_dynamic heads: {formatted}",
301
+ flush=True,
302
+ )
303
+
304
+
305
+ class AHAQwen3Config(Qwen3Config):
306
+ """Qwen3Config with AHA-specific parameters."""
307
+ model_type = "aha_qwen3"
308
+
309
+ def __init__(
310
+ self,
311
+ aha_window_size=AHA_WINDOW_SIZE,
312
+ aha_lambda=AHA_LAMBDA,
313
+ aha_distill_weight=AHA_DISTILL_WEIGHT,
314
+ aha_ce_weight=AHA_CE_WEIGHT,
315
+ aha_gate_target=AHA_GATE_TARGET,
316
+ aha_reg_weight=-1.0,
317
+ aha_force_gate_value=None,
318
+ aha_mode=AHA_MODE,
319
+ duo_sink_size=DUO_SINK_SIZE,
320
+ duo_recent_size=DUO_RECENT_SIZE,
321
+ duo_alpha_init=DUO_ALPHA_INIT,
322
+ aha_local_kind=AHA_LOCAL_KIND,
323
+ aha_router_granularity=AHA_ROUTER_GRANULARITY,
324
+ **kwargs,
325
+ ):
326
+ super().__init__(**kwargs)
327
+ self.aha_window_size = aha_window_size
328
+ self.aha_lambda = aha_lambda
329
+ # Per-layer attention distillation weight. When > 0 and model is
330
+ # training, each layer contributes ``mean(((1-g)·(global-local))**2)``
331
+ # to the total loss. This is a dense per-(token, head) signal that
332
+ # pushes the gate toward 1 where SWA is insufficient to approximate
333
+ # full attention. Cost: ~0 extra forward compute (global/local are
334
+ # already computed for the mix).
335
+ self.aha_distill_weight = aha_distill_weight
336
+ # Language-modeling CE weight for the training objective. Default 1.0
337
+ # keeps the standard SFT behaviour. Set to 0.0 to drop CE entirely
338
+ # and train the gate purely from ``aux + distill`` (useful when the
339
+ # backbone is frozen and we only want to shape the gate). Evaluation
340
+ # always reports unweighted ``ce_loss`` regardless of this setting.
341
+ self.aha_ce_weight = aha_ce_weight
342
+ # Direct Duo-style sparsity regularizer. When >= 0, this replaces the
343
+ # legacy hinge term and uses ``aha_reg_weight * mean(gate_soft)``.
344
+ # Default -1 keeps older checkpoints on their original hinge objective.
345
+ self.aha_reg_weight = float(aha_reg_weight)
346
+ # Optional override used by Duo-style dynamic distillation. When set
347
+ # to 1.0, dynamic mode behaves as a full-attention teacher for one
348
+ # forward; None keeps the learned gate.
349
+ self.aha_force_gate_value = aha_force_gate_value
350
+ # Target ceiling on mean(gate_soft). Aux loss is hinge-shaped:
351
+ # ``λ · max(0, mean(gate_soft) - τ)``. Setting τ < 1.0 guarantees
352
+ # the gate cannot drop below ``1 - τ`` local-attention fraction
353
+ # in steady state -- the failure mode we observed with the legacy
354
+ # unconditional aux, where ``λ = 1.0`` drove mean(gate_hard) to
355
+ # 0.005 (99.5% local) and killed long-context accuracy
356
+ # (gsm8k 74% -> 19%). Default 1.0 is a no-op for backward
357
+ # compatibility with checkpoints trained on the old loss.
358
+ self.aha_gate_target = aha_gate_target
359
+ # DuoAttention-style baseline. ``dynamic`` keeps the original per-
360
+ # (token, kv_head) MLP gate. ``duo`` replaces it with a per-(layer,
361
+ # kv_head) learnable scalar and swaps the local branch's mask for a
362
+ # streaming (sink + recent) pattern. ``duo_dynamic`` keeps the Duo
363
+ # static mask for KV-capacity planning, but applies a dynamic AHA gate
364
+ # only on the static-full heads: effective_full = duo_full AND dyn_gate.
365
+ # Sink / recent sizes are fixed at train time; set ``duo_sink_size=0``
366
+ # for the sink-ablation run.
367
+ if aha_mode not in ("dynamic", "duo", "duo_dynamic"):
368
+ raise ValueError(
369
+ f"aha_mode must be 'dynamic', 'duo', or 'duo_dynamic', got {aha_mode!r}"
370
+ )
371
+ self.aha_mode = aha_mode
372
+ if aha_router_granularity not in ("token", "token_kv_head"):
373
+ raise ValueError(
374
+ "aha_router_granularity must be 'token' or 'token_kv_head', "
375
+ f"got {aha_router_granularity!r}"
376
+ )
377
+ # Persist this even for static Duo checkpoints. Dynamic AHA uses it to
378
+ # choose the native q_proj gate-row count; old configs omit the field
379
+ # and therefore retain the historical token_kv_head behavior.
380
+ self.aha_router_granularity = aha_router_granularity
381
+ self.duo_sink_size = int(duo_sink_size)
382
+ self.duo_recent_size = int(duo_recent_size)
383
+ self.duo_alpha_init = float(duo_alpha_init)
384
+ if aha_local_kind not in ("sliding_window", "sink_recent"):
385
+ raise ValueError(
386
+ f"aha_local_kind must be 'sliding_window' or 'sink_recent', "
387
+ f"got {aha_local_kind!r}"
388
+ )
389
+ # When aha_mode="duo" the local branch is always sink+recent regardless
390
+ # of this field (forced to the historical DuoAttention behaviour). The
391
+ # field only controls the dynamic mode's local mask.
392
+ self.aha_local_kind = aha_local_kind
393
+
394
+
395
+ def _build_streaming_causal_mask(
396
+ seq_len_q: int,
397
+ seq_len_kv: int,
398
+ sink_size: int,
399
+ recent_size: int,
400
+ device: torch.device,
401
+ dtype: torch.dtype,
402
+ kv_offset: int = 0,
403
+ ) -> torch.Tensor:
404
+ """Construct a streaming attention mask (sink + recent, causal).
405
+
406
+ Returns an additive mask of shape ``[1, 1, Q, K]`` where
407
+ disallowed positions are ``finfo(dtype).min`` and allowed positions
408
+ are ``0``. A query at position ``q`` (absolute in the sequence,
409
+ with ``kv_offset`` tokens already in the KV cache) attends to a key
410
+ at position ``k`` iff ``k <= q_abs`` AND (``k < sink_size`` OR
411
+ ``q_abs - k < recent_size``).
412
+ """
413
+ q_abs = torch.arange(seq_len_q, device=device).unsqueeze(-1) + kv_offset # [Q, 1]
414
+ k_abs = torch.arange(seq_len_kv, device=device).unsqueeze(0) # [1, K]
415
+ causal = k_abs <= q_abs
416
+ sink = k_abs < sink_size if sink_size > 0 else torch.zeros_like(causal)
417
+ recent = (q_abs - k_abs) < recent_size
418
+ allowed = causal & (sink | recent)
419
+ mask = torch.zeros(seq_len_q, seq_len_kv, dtype=dtype, device=device)
420
+ mask.masked_fill_(~allowed, torch.finfo(dtype).min)
421
+ return mask.unsqueeze(0).unsqueeze(0)
422
+
423
+
424
+ class AHAQwen3Attention(nn.Module):
425
+ """Qwen3 attention with AHA gate for per-head local/global routing."""
426
+
427
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
428
+ super().__init__()
429
+ self.config = config
430
+ self.layer_idx = layer_idx
431
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
432
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
433
+ self.scaling = self.head_dim ** -0.5
434
+ self.attention_dropout = config.attention_dropout
435
+ self.is_causal = True
436
+ self.aha_window_size = config.aha_window_size
437
+
438
+ # q_proj: extra router outputs for either one shared per-token gate or
439
+ # one per-(token, KV-head) gate.
440
+ # DUO mode uses a per-(layer, kv_head) static scalar instead, so
441
+ # q_proj keeps its vanilla Qwen3 shape and is binary-compatible
442
+ # with the base model checkpoint.
443
+ self._has_dyn_gate_logits = getattr(config, "aha_mode", "dynamic") in (
444
+ "dynamic",
445
+ "duo_dynamic",
446
+ )
447
+ self.aha_router_outputs = aha_router_output_size(config)
448
+ q_out_dim = config.num_attention_heads * self.head_dim
449
+ if self._has_dyn_gate_logits:
450
+ q_out_dim += self.aha_router_outputs
451
+ self.q_proj = nn.Linear(
452
+ config.hidden_size,
453
+ q_out_dim,
454
+ bias=config.attention_bias,
455
+ )
456
+ self.k_proj = nn.Linear(
457
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
458
+ )
459
+ self.v_proj = nn.Linear(
460
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
461
+ )
462
+ self.o_proj = nn.Linear(
463
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
464
+ )
465
+ # Qwen3 applies q_norm/k_norm per head_dim (not full projection)
466
+ self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
467
+ self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps)
468
+
469
+ # DuoAttention baseline: per-(layer, kv_head) learnable scalar.
470
+ # Populated in ``duo`` and ``duo_dynamic``. In ``duo_dynamic`` this
471
+ # scalar is the frozen Duo capacity mask; the dynamic gate is only
472
+ # allowed to close heads where this scalar says "full". Registered
473
+ # as ``full_attention_heads`` to match the name used in the
474
+ # official DuoAttention repo (``duo_attn.patch.llama``), so
475
+ # per-head statistics (e.g. retrieval heads vs streaming heads)
476
+ # can be read back with the same tooling.
477
+ if getattr(config, "aha_mode", "dynamic") in ("duo", "duo_dynamic"):
478
+ init = float(getattr(config, "duo_alpha_init", 1.0))
479
+ self.full_attention_heads = nn.Parameter(
480
+ torch.full((config.num_key_value_heads,), init, dtype=torch.float32)
481
+ )
482
+ else:
483
+ self.full_attention_heads = None
484
+ force_full_mask = torch.zeros(config.num_key_value_heads, dtype=torch.bool)
485
+ for raw in os.environ.get("AHA_FORCE_FULL_HEADS", "").split(","):
486
+ raw = raw.strip()
487
+ if not raw:
488
+ continue
489
+ layer_s, head_s = raw.split(":", 1)
490
+ if int(layer_s) == int(layer_idx):
491
+ force_full_mask[int(head_s)] = True
492
+ self.register_buffer(
493
+ "_aha_force_full_heads_mask",
494
+ force_full_mask,
495
+ persistent=False,
496
+ )
497
+
498
+ def forward(
499
+ self,
500
+ hidden_states: torch.Tensor,
501
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
502
+ attention_mask: Optional[dict], # dict with "global" and "local" keys
503
+ past_key_values: Optional[Cache] = None,
504
+ cache_position: Optional[torch.LongTensor] = None,
505
+ **kwargs,
506
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:
507
+ input_shape = hidden_states.shape[:-1]
508
+ hidden_shape = (*input_shape, -1, self.head_dim)
509
+
510
+ # === Q projection + (optional) gate extraction ===
511
+ q_proj_out = self.q_proj(hidden_states)
512
+ if self._has_dyn_gate_logits:
513
+ query_states, gate = torch.split(
514
+ q_proj_out,
515
+ [
516
+ self.config.num_attention_heads * self.head_dim,
517
+ self.aha_router_outputs,
518
+ ],
519
+ dim=-1,
520
+ )
521
+ else:
522
+ query_states = q_proj_out
523
+ # DUO mode: use a zero placeholder so downstream dtype/device
524
+ # checks stay unchanged. The actual gate comes from the
525
+ # per-kv-head scalar ``full_attention_heads`` below.
526
+ gate = torch.zeros(
527
+ *input_shape, self.config.num_key_value_heads,
528
+ dtype=query_states.dtype, device=query_states.device,
529
+ )
530
+
531
+ # Qwen3-style: norm then reshape then transpose
532
+ query_states = self.q_norm(query_states.view(hidden_shape)).transpose(1, 2)
533
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
534
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
535
+
536
+ cos, sin = position_embeddings
537
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
538
+
539
+ if past_key_values is not None:
540
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
541
+ key_states, value_states = past_key_values.update(
542
+ key_states, value_states, self.layer_idx, cache_kwargs
543
+ )
544
+
545
+ # === Gate computation ===
546
+ # gate shape: (B, Q, H_kv) — per-(token, kv_head) in dynamic mode,
547
+ # or per-kv_head broadcast to (B, Q, H_kv) in DUO mode.
548
+ # At inference, threshold is read from env var AHA_GATE_HARD_THRESHOLD
549
+ # so users can trade sparsity for quality without re-training.
550
+ # Default 0.5 keeps training-time behaviour. Lowering to e.g. 0.35
551
+ # lets more (token, kv_head) pairs route through global attention,
552
+ # which recovers long-CoT quality at the cost of sparsity.
553
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
554
+ if aha_mode == "duo":
555
+ # DuoAttention: alpha is per-(layer, kv_head) learnable scalar.
556
+ # gate_soft = clamp(alpha, 0, 1); gate_hard = (alpha > 0.5).
557
+ # Both broadcast to (B, Q, H_kv) for a uniform downstream API
558
+ # (so the distill / aux / blend paths do not need to branch).
559
+ qhead_alpha = getattr(self, "_aha_qhead_full_attention_heads", None)
560
+ if qhead_alpha is not None:
561
+ # Eval-only oracle diagnostic: route at Q-head granularity
562
+ # instead of KV-head/GQA-group granularity. This does not
563
+ # represent a KV-capacity-saving deployment point; it isolates
564
+ # whether GQA grouping itself is the quality bottleneck.
565
+ alpha = qhead_alpha.to(gate.dtype).clamp(0.0, 1.0)
566
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
567
+ import os as _os
568
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
569
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
570
+ gate_ste = gate_hard
571
+
572
+ else:
573
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
574
+ gate_soft = alpha.view(1, 1, -1).expand(*input_shape, -1)
575
+ if self.training:
576
+ gate_hard = (alpha > 0.5).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
577
+ # Straight-through estimator through the clamped alpha so
578
+ # L1 / distill gradients flow into the scalar.
579
+ gate_ste = gate_soft
580
+ else:
581
+ import os as _os
582
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
583
+ gate_hard = (alpha > _thr).to(gate.dtype).view(1, 1, -1).expand(*input_shape, -1)
584
+ gate_ste = gate_hard
585
+ elif aha_mode == "duo_dynamic":
586
+ # Duo + AHA-on-full-heads:
587
+ # duo_full_mask = 1 for static Duo full/retrieval heads
588
+ # dyn_gate = per-token AHA decision inside those heads
589
+ # effective_full = duo_full_mask AND dyn_gate
590
+ #
591
+ # Duo streaming heads are locked to local/streaming forever. This
592
+ # preserves Duo's KV-capacity saving: only static-full heads need a
593
+ # full KV cache. ``aha_force_gate_value=1`` therefore reproduces
594
+ # the static Duo deployment point exactly, not all-full attention.
595
+ alpha = self.full_attention_heads.to(gate.dtype).clamp(0.0, 1.0)
596
+ duo_full = (alpha > 0.5).to(gate.dtype).view(1, 1, -1)
597
+ dyn_soft = torch.sigmoid(gate)
598
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
599
+ if force_gate_value is not None:
600
+ dyn_soft = torch.full_like(dyn_soft, float(force_gate_value))
601
+ gate_soft = dyn_soft * duo_full
602
+ gate_hard = gate_soft
603
+ gate_ste = gate_soft
604
+ elif self.training:
605
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
606
+ gate_soft = dyn_soft * duo_full
607
+ dyn_hard = (dyn_soft > train_thr).to(gate.dtype)
608
+ gate_hard = dyn_hard * duo_full
609
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
610
+ else:
611
+ import os as _os
612
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
613
+ gate_soft = dyn_soft * duo_full
614
+ dyn_hard = (dyn_soft > _thr).to(gate.dtype)
615
+ gate_hard = dyn_hard * duo_full
616
+ gate_ste = gate_hard
617
+ force_full = self._aha_force_full_heads_mask.to(gate.dtype).view(1, 1, -1)
618
+ if bool(force_full.any().item()):
619
+ force_full = force_full * duo_full
620
+ gate_hard = torch.maximum(gate_hard, force_full)
621
+ gate_ste = torch.maximum(gate_ste, force_full)
622
+ else:
623
+ gate_soft = torch.sigmoid(gate)
624
+ force_gate_value = getattr(self.config, "aha_force_gate_value", None)
625
+ if force_gate_value is not None:
626
+ force = float(force_gate_value)
627
+ gate_soft = torch.full_like(gate_soft, force)
628
+ gate_hard = torch.full_like(gate_soft, force)
629
+ gate_ste = gate_soft
630
+ elif self.training:
631
+ train_thr = float(os.environ.get("AHA_TRAIN_GATE_HARD_THRESHOLD", "0.5"))
632
+ gate_hard = (gate_soft > train_thr).to(gate.dtype)
633
+ gate_ste = gate_hard + (gate_soft - gate_soft.detach())
634
+ else:
635
+ import os as _os
636
+ _thr = float(_os.environ.get("AHA_GATE_HARD_THRESHOLD", "0.5"))
637
+ gate_hard = (gate_soft > _thr).to(gate.dtype)
638
+ gate_ste = gate_hard
639
+
640
+ # Normalize the downstream contract to effective
641
+ # token x KV-head decisions. In shared-token mode there is only one
642
+ # learned logit per token, but regularization, sparsity accounting, and
643
+ # attention mixing all see the same KV-head denominator as the
644
+ # head-granular arm.
645
+ if gate_soft.shape[-1] == 1 and self.config.num_key_value_heads != 1:
646
+ effective_shape = (*gate_soft.shape[:-1], self.config.num_key_value_heads)
647
+ gate_soft = gate_soft.expand(effective_shape)
648
+ gate_hard = gate_hard.expand(effective_shape)
649
+ gate_ste = gate_ste.expand(effective_shape)
650
+
651
+ if (
652
+ aha_mode in ("duo", "duo_dynamic")
653
+ and not self.training
654
+ and os.environ.get("AHA_DUO_PREFILL_FULL", "0") == "1"
655
+ and query_states.shape[-2] == key_states.shape[-2]
656
+ ):
657
+ # Diagnostic parity with the official DuoAttention HF eval path:
658
+ # initial prefill is full attention; streaming heads are only
659
+ # localized during subsequent decode steps.
660
+ gate_hard = torch.ones_like(gate_hard)
661
+ gate_ste = torch.ones_like(gate_ste)
662
+
663
+ if not self.training:
664
+ phase = (
665
+ "decode"
666
+ if hidden_states.shape[1] == 1 and key_states.shape[-2] > 1
667
+ else "prefill"
668
+ )
669
+ _track_aha_inference_sparsity(
670
+ gate_hard, gate_soft, self.layer_idx, phase,
671
+ getattr(
672
+ self.config,
673
+ "aha_router_granularity",
674
+ AHA_ROUTER_GRANULARITY,
675
+ ),
676
+ self.aha_router_outputs if self._has_dyn_gate_logits
677
+ else self.config.num_key_value_heads,
678
+ )
679
+
680
+ # === Attention: double_mix (global + local, blended by gate) ===
681
+ from transformers.models.qwen3.modeling_qwen3 import eager_attention_forward
682
+
683
+ attention_interface: Callable = eager_attention_forward
684
+ if self.config._attn_implementation != "eager":
685
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
686
+
687
+ # Global attention (full context)
688
+ global_attn_output, _ = attention_interface(
689
+ self, query_states, key_states, value_states,
690
+ attention_mask["global"],
691
+ dropout=0.0 if not self.training else self.attention_dropout,
692
+ scaling=self.scaling,
693
+ **kwargs,
694
+ )
695
+
696
+ if getattr(self.config, "_aha_teacher_full_fastpath", False):
697
+ attn_output = global_attn_output.reshape(*input_shape, -1).contiguous()
698
+ attn_output = self.o_proj(attn_output)
699
+ return attn_output, gate_soft, gate_hard, None
700
+
701
+ # Local / streaming branch. The local mask was pre-built by the
702
+ # model's ``forward`` either as sliding-window (legacy dynamic mode
703
+ # default) or sink+recent (duo mode, and dynamic mode when
704
+ # ``aha_local_kind="sink_recent"``). Only pass ``sliding_window`` to
705
+ # the SDPA backend when the mask is the sliding-window kind, otherwise
706
+ # the backend will double-mask and wipe out the sink positions.
707
+ local_attn_kwargs = dict(
708
+ dropout=0.0 if not self.training else self.attention_dropout,
709
+ scaling=self.scaling,
710
+ )
711
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
712
+ self.config, "aha_local_kind", "sliding_window"
713
+ )
714
+ if local_kind == "sliding_window":
715
+ local_attn_kwargs["sliding_window"] = self.aha_window_size
716
+ local_attn_output, _ = attention_interface(
717
+ self, query_states, key_states, value_states,
718
+ attention_mask["local"],
719
+ **local_attn_kwargs,
720
+ **kwargs,
721
+ )
722
+
723
+ # Blend: normal gate_ste is (B, Q, H_kv); broadcast to (B, Q, H, 1)
724
+ # by repeating each kv_head decision. The optional qhead oracle above
725
+ # already emits (B, Q, H), so it bypasses this broadcast.
726
+ if gate_ste.shape[-1] == self.config.num_attention_heads:
727
+ g_bqh = gate_ste
728
+ else:
729
+ g_bqh = gate_ste.repeat_interleave(self.num_key_value_groups, dim=-1)
730
+ attn_output = (
731
+ global_attn_output * g_bqh.unsqueeze(-1)
732
+ + local_attn_output * (1 - g_bqh.unsqueeze(-1))
733
+ )
734
+
735
+ # Per-layer attention distillation signal. Returns a scalar so the
736
+ # outer model can aggregate across layers. When gate → 1 the term
737
+ # vanishes; when gate → 0 on positions where global/local disagree
738
+ # strongly, the gradient pushes gate back toward 1.
739
+ #
740
+ # ``diff`` is ``detach``-ed so distill only trains the gate; it does
741
+ # not reshape attention outputs through the backbone. This also
742
+ # prevents the squared-difference graph from retaining activations
743
+ # for all 28 layers' global/local branches, which was the source of
744
+ # OOM when gradient checkpointing is disabled on the custom forward.
745
+ distill_per_layer: Optional[torch.Tensor] = None
746
+ if self.training and getattr(self.config, "aha_distill_weight", 0.0) > 0.0:
747
+ with torch.no_grad():
748
+ diff_sq = (global_attn_output - local_attn_output).float() ** 2
749
+ one_minus_g_sq = ((1.0 - g_bqh.float()) ** 2).unsqueeze(-1)
750
+ distill_per_layer = (one_minus_g_sq * diff_sq).mean()
751
+
752
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
753
+ attn_output = self.o_proj(attn_output)
754
+ return attn_output, gate_soft, gate_hard, distill_per_layer
755
+
756
+
757
+ class AHAQwen3DecoderLayer(GradientCheckpointingLayer):
758
+ """Decoder layer with gradient-checkpointing support.
759
+
760
+ Inheriting from ``GradientCheckpointingLayer`` lets HuggingFace Trainer's
761
+ ``gradient_checkpointing_enable`` take effect on this layer without any
762
+ explicit ``_gradient_checkpointing_func`` call in the outer forward.
763
+ """
764
+
765
+ def __init__(self, config: AHAQwen3Config, layer_idx: int):
766
+ super().__init__()
767
+ self.hidden_size = config.hidden_size
768
+ self.self_attn = AHAQwen3Attention(config=config, layer_idx=layer_idx)
769
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3MLP
770
+ self.mlp = Qwen3MLP(config)
771
+ self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
772
+ self.post_attention_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
773
+
774
+ def forward(
775
+ self,
776
+ hidden_states: torch.Tensor,
777
+ attention_mask: Optional[dict] = None,
778
+ position_ids: Optional[torch.LongTensor] = None,
779
+ past_key_values: Optional[Cache] = None,
780
+ use_cache: Optional[bool] = False,
781
+ cache_position: Optional[torch.LongTensor] = None,
782
+ position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None,
783
+ **kwargs,
784
+ ):
785
+ residual = hidden_states
786
+ hidden_states = self.input_layernorm(hidden_states)
787
+ hidden_states, gate_soft, gate_hard, distill_per_layer = self.self_attn(
788
+ hidden_states=hidden_states,
789
+ attention_mask=attention_mask,
790
+ position_ids=position_ids,
791
+ past_key_values=past_key_values,
792
+ use_cache=use_cache,
793
+ cache_position=cache_position,
794
+ position_embeddings=position_embeddings,
795
+ **kwargs,
796
+ )
797
+ hidden_states = residual + hidden_states
798
+
799
+ residual = hidden_states
800
+ hidden_states = self.post_attention_layernorm(hidden_states)
801
+ hidden_states = self.mlp(hidden_states)
802
+ hidden_states = residual + hidden_states
803
+ return hidden_states, gate_soft, gate_hard, distill_per_layer
804
+
805
+
806
+ @dataclass
807
+ class AHACausalLMOutputWithPast(CausalLMOutputWithPast):
808
+ """Causal LM output with AHA loss breakdown and gate density stats (training / eval with labels)."""
809
+
810
+ ce_loss: Optional[torch.FloatTensor] = None
811
+ gate_aux_loss: Optional[torch.FloatTensor] = None
812
+ distill_loss: Optional[torch.FloatTensor] = None
813
+ gate_soft_mean: Optional[torch.FloatTensor] = None
814
+ gate_hard_mean: Optional[torch.FloatTensor] = None
815
+
816
+
817
+ class AHAModelOutputWithPast(BaseModelOutputWithPast):
818
+ def __init__(self, last_hidden_state, past_key_values=None, hidden_states=None, attentions=None,
819
+ all_gate_soft=None, all_gate_hard=None, all_distill_per_layer=None):
820
+ super().__init__(last_hidden_state=last_hidden_state, past_key_values=past_key_values,
821
+ hidden_states=hidden_states, attentions=attentions)
822
+ self.all_gate_soft = all_gate_soft
823
+ self.all_gate_hard = all_gate_hard
824
+ # Tuple of per-layer distill scalars, or empty tuple when distillation is off.
825
+ self.all_distill_per_layer = all_distill_per_layer if all_distill_per_layer is not None else ()
826
+
827
+
828
+ class AHAQwen3Model(Qwen3Model):
829
+ config_class = AHAQwen3Config
830
+
831
+ def __init__(self, config: AHAQwen3Config):
832
+ from transformers import PreTrainedModel
833
+ PreTrainedModel.__init__(self, config)
834
+ self.padding_idx = config.pad_token_id
835
+ self.vocab_size = config.vocab_size
836
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
837
+ self.layers = nn.ModuleList(
838
+ [AHAQwen3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
839
+ )
840
+ self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
841
+ from transformers.models.qwen3.modeling_qwen3 import Qwen3RotaryEmbedding
842
+ self.rotary_emb = Qwen3RotaryEmbedding(config=config)
843
+ self.gradient_checkpointing = False
844
+ self.post_init()
845
+
846
+ def forward(
847
+ self,
848
+ input_ids: Optional[torch.LongTensor] = None,
849
+ attention_mask: Optional[torch.Tensor] = None,
850
+ position_ids: Optional[torch.LongTensor] = None,
851
+ past_key_values: Optional[Cache] = None,
852
+ inputs_embeds: Optional[torch.FloatTensor] = None,
853
+ use_cache: Optional[bool] = None,
854
+ cache_position: Optional[torch.LongTensor] = None,
855
+ **kwargs,
856
+ ) -> AHAModelOutputWithPast:
857
+ if (input_ids is None) ^ (inputs_embeds is not None):
858
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
859
+ if inputs_embeds is None:
860
+ inputs_embeds = self.embed_tokens(input_ids)
861
+ if use_cache and past_key_values is None:
862
+ past_key_values = DynamicCache(config=self.config)
863
+ if cache_position is None:
864
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
865
+ cache_position = torch.arange(
866
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
867
+ )
868
+ if position_ids is None:
869
+ position_ids = cache_position.unsqueeze(0)
870
+
871
+ # Build two masks: global (full causal) and local (sliding window
872
+ # for dynamic mode, streaming sink+recent for DUO mode).
873
+ orig_sliding_window = getattr(self.config, "sliding_window", None)
874
+ self.config.sliding_window = self.config.aha_window_size
875
+ mask_kwargs = {
876
+ "config": self.config,
877
+ "input_embeds": inputs_embeds,
878
+ "attention_mask": attention_mask,
879
+ "cache_position": cache_position,
880
+ "past_key_values": past_key_values,
881
+ "position_ids": position_ids,
882
+ }
883
+ global_mask = create_causal_mask(**mask_kwargs)
884
+ aha_mode = getattr(self.config, "aha_mode", "dynamic")
885
+ # duo / duo_dynamic force sink_recent for backward compat. dynamic mode
886
+ # reads the local kind from config (default sliding_window for legacy
887
+ # Bv3 ckpts; hot-started ckpts from duo set sink_recent — see
888
+ # §9.4.13.3).
889
+ local_kind = "sink_recent" if aha_mode in ("duo", "duo_dynamic") else getattr(
890
+ self.config, "aha_local_kind", "sliding_window"
891
+ )
892
+ if local_kind == "sink_recent":
893
+ seq_len_q = inputs_embeds.shape[1]
894
+ past_seen = past_key_values.get_seq_length() if past_key_values is not None else 0
895
+ seq_len_kv = seq_len_q + past_seen
896
+ local_mask = _build_streaming_causal_mask(
897
+ seq_len_q=seq_len_q,
898
+ seq_len_kv=seq_len_kv,
899
+ sink_size=int(getattr(self.config, "duo_sink_size", 64)),
900
+ recent_size=int(getattr(self.config, "duo_recent_size", 256)),
901
+ device=inputs_embeds.device,
902
+ dtype=inputs_embeds.dtype,
903
+ kv_offset=past_seen,
904
+ )
905
+ else:
906
+ local_mask = create_sliding_window_causal_mask(**mask_kwargs)
907
+ causal_mask_dict = {"global": global_mask, "local": local_mask}
908
+ self.config.sliding_window = orig_sliding_window
909
+
910
+ hidden_states = inputs_embeds
911
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
912
+
913
+ all_gate_soft = ()
914
+ all_gate_hard = ()
915
+ all_distill_per_layer: tuple[torch.Tensor, ...] = ()
916
+ for decoder_layer in self.layers:
917
+ hidden_states, gate_soft, gate_hard, distill_per_layer = decoder_layer(
918
+ hidden_states,
919
+ attention_mask=causal_mask_dict,
920
+ position_ids=position_ids,
921
+ past_key_values=past_key_values,
922
+ use_cache=use_cache,
923
+ cache_position=cache_position,
924
+ position_embeddings=position_embeddings,
925
+ **kwargs,
926
+ )
927
+ all_gate_soft += (gate_soft,)
928
+ all_gate_hard += (gate_hard,)
929
+ if distill_per_layer is not None:
930
+ all_distill_per_layer += (distill_per_layer,)
931
+
932
+ hidden_states = self.norm(hidden_states)
933
+ return AHAModelOutputWithPast(
934
+ last_hidden_state=hidden_states,
935
+ past_key_values=past_key_values if use_cache else None,
936
+ all_gate_soft=all_gate_soft,
937
+ all_gate_hard=all_gate_hard,
938
+ all_distill_per_layer=all_distill_per_layer,
939
+ )
940
+
941
+
942
+ class AHAQwen3ForCausalLM(Qwen3ForCausalLM):
943
+ config_class = AHAQwen3Config
944
+ _tied_weights_keys = ["lm_head.weight"]
945
+
946
+ def __init__(self, config: AHAQwen3Config):
947
+ # Skip Qwen3ForCausalLM.__init__ which creates Qwen3Model
948
+ # Go to PreTrainedModel.__init__ directly
949
+ from transformers import PreTrainedModel
950
+ PreTrainedModel.__init__(self, config)
951
+ self.model = AHAQwen3Model(config)
952
+ self.vocab_size = config.vocab_size
953
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
954
+ self.post_init()
955
+
956
+ def forward(
957
+ self,
958
+ input_ids: Optional[torch.LongTensor] = None,
959
+ attention_mask: Optional[torch.Tensor] = None,
960
+ position_ids: Optional[torch.LongTensor] = None,
961
+ past_key_values: Optional[Cache] = None,
962
+ inputs_embeds: Optional[torch.FloatTensor] = None,
963
+ labels: Optional[torch.LongTensor] = None,
964
+ use_cache: Optional[bool] = None,
965
+ cache_position: Optional[torch.LongTensor] = None,
966
+ logits_to_keep: Union[int, torch.Tensor] = 0,
967
+ **kwargs,
968
+ ) -> AHACausalLMOutputWithPast:
969
+ outputs = self.model(
970
+ input_ids=input_ids,
971
+ attention_mask=attention_mask,
972
+ position_ids=position_ids,
973
+ past_key_values=past_key_values,
974
+ inputs_embeds=inputs_embeds,
975
+ use_cache=use_cache,
976
+ cache_position=cache_position,
977
+ **kwargs,
978
+ )
979
+ hidden_states = outputs.last_hidden_state
980
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
981
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
982
+
983
+ loss = None
984
+ ce_loss = None
985
+ gate_aux_loss = None
986
+ distill_loss = None
987
+ gate_soft_mean = None
988
+ gate_hard_mean = None
989
+
990
+ if labels is not None:
991
+ from torch.nn import CrossEntropyLoss
992
+ shift_logits = logits[..., :-1, :].contiguous()
993
+ shift_labels = labels[..., 1:].contiguous()
994
+ loss_fct = CrossEntropyLoss()
995
+ ce_loss = loss_fct(shift_logits.view(-1, self.vocab_size), shift_labels.view(-1))
996
+
997
+ all_gate_soft = outputs.all_gate_soft
998
+ all_gate_hard = outputs.all_gate_hard
999
+ if labels is not None and len(all_gate_soft) > 0 and len(all_gate_hard) > 0:
1000
+ gate_soft_stack = torch.stack(
1001
+ [g.to(hidden_states.device) for g in all_gate_soft], dim=-1
1002
+ ).float()
1003
+ gate_hard_stack = torch.stack(
1004
+ [g.to(hidden_states.device).float() for g in all_gate_hard], dim=-1
1005
+ )
1006
+ shift_gate_soft = gate_soft_stack[:, :-1, :].contiguous()
1007
+ shift_gate_hard = gate_hard_stack[:, :-1, :].contiguous()
1008
+ # Match the CE/PPL population exactly. The old unmasked mean
1009
+ # counted padded sequence positions as real gate decisions, which
1010
+ # made the training sparsity curve disagree with inference-time
1011
+ # sparsity on variable-length batches.
1012
+ valid_gate = shift_labels.ne(-100)
1013
+ if attention_mask is not None:
1014
+ valid_gate = valid_gate & attention_mask[:, 1:].to(torch.bool)
1015
+ if bool(valid_gate.any()):
1016
+ valid_gate = valid_gate[..., None, None].to(shift_gate_soft.dtype)
1017
+ normalizer = (
1018
+ valid_gate.sum()
1019
+ * shift_gate_soft.shape[-2]
1020
+ * shift_gate_soft.shape[-1]
1021
+ )
1022
+ gate_soft_mean = (shift_gate_soft * valid_gate).sum() / normalizer
1023
+ gate_hard_mean = (shift_gate_hard * valid_gate).sum() / normalizer
1024
+ else:
1025
+ gate_soft_mean = shift_gate_soft.mean()
1026
+ gate_hard_mean = shift_gate_hard.mean()
1027
+
1028
+ # Per-layer distillation loss: mean over layers of
1029
+ # ``mean(((1-g)·(global-local))**2)``. Populated only when training
1030
+ # with ``config.aha_distill_weight > 0`` (see AHAQwen3Attention).
1031
+ all_distill = outputs.all_distill_per_layer
1032
+ if self.training and len(all_distill) > 0:
1033
+ distill_loss = torch.stack(list(all_distill)).mean()
1034
+
1035
+ # === Loss assembly ===
1036
+ # Eval path: standard LM loss for compatibility with HF metrics.
1037
+ # Train path: weighted sum of (ce, gate_aux, distill) so callers can
1038
+ # zero any term by setting its weight to 0 (e.g. ``aha_ce_weight=0``
1039
+ # to train the gate purely from ``aux + distill``).
1040
+ if not self.training:
1041
+ loss = ce_loss
1042
+ else:
1043
+ ce_weight = float(getattr(self.config, "aha_ce_weight", 1.0))
1044
+ distill_weight = float(getattr(self.config, "aha_distill_weight", 0.0))
1045
+ terms: list[torch.Tensor] = []
1046
+ if ce_loss is not None and ce_weight > 0.0:
1047
+ terms.append(ce_weight * ce_loss)
1048
+ if gate_soft_mean is not None:
1049
+ reg_weight = float(getattr(self.config, "aha_reg_weight", -1.0))
1050
+ if reg_weight >= 0.0:
1051
+ # Duo-style direct sparsity term:
1052
+ # alpha/gate near 1 means full/global attention, so penalize
1053
+ # mean gate usage directly.
1054
+ gate_aux_loss = reg_weight * gate_soft_mean
1055
+ else:
1056
+ # Legacy hinge aux: penalise only the excess above the target
1057
+ # ceiling. Below ``τ`` the gradient is zero, so CE and
1058
+ # distill freely shape where the gate opens without an
1059
+ # unbounded downward push from aux. See AHAQwen3Config
1060
+ # for the rationale and the collapse that motivated it.
1061
+ gate_target = float(getattr(self.config, "aha_gate_target", 1.0))
1062
+ excess = torch.clamp(gate_soft_mean - gate_target, min=0.0)
1063
+ gate_aux_loss = self.config.aha_lambda * excess
1064
+ terms.append(gate_aux_loss)
1065
+ if distill_loss is not None and distill_weight > 0.0:
1066
+ terms.append(distill_weight * distill_loss)
1067
+ if terms:
1068
+ loss = terms[0]
1069
+ for t in terms[1:]:
1070
+ loss = loss + t
1071
+
1072
+ return AHACausalLMOutputWithPast(
1073
+ loss=loss,
1074
+ logits=logits,
1075
+ past_key_values=outputs.past_key_values,
1076
+ ce_loss=ce_loss,
1077
+ gate_aux_loss=gate_aux_loss,
1078
+ distill_loss=distill_loss,
1079
+ gate_soft_mean=gate_soft_mean,
1080
+ gate_hard_mean=gate_hard_mean,
1081
+ )
1082
+
1083
+ @classmethod
1084
+ def from_pretrained_qwen3(cls, qwen3_path: str, aha_window_size: int = AHA_WINDOW_SIZE,
1085
+ aha_lambda: float = AHA_LAMBDA,
1086
+ aha_distill_weight: float = AHA_DISTILL_WEIGHT,
1087
+ aha_ce_weight: float = AHA_CE_WEIGHT,
1088
+ aha_gate_target: float = AHA_GATE_TARGET,
1089
+ aha_reg_weight: float = -1.0,
1090
+ aha_force_gate_value=None,
1091
+ aha_mode: str = AHA_MODE,
1092
+ duo_sink_size: int = DUO_SINK_SIZE,
1093
+ duo_recent_size: int = DUO_RECENT_SIZE,
1094
+ duo_alpha_init: float = DUO_ALPHA_INIT,
1095
+ aha_local_kind: str = AHA_LOCAL_KIND,
1096
+ aha_router_granularity: str = AHA_ROUTER_GRANULARITY,
1097
+ **kwargs):
1098
+ """Load a pretrained Qwen3 model and convert to AHA-Qwen3.
1099
+
1100
+ Initializes the gate weights in q_proj randomly (the extra num_heads outputs).
1101
+ All other weights are copied from the pretrained model.
1102
+ """
1103
+ # Load original Qwen3
1104
+ original = Qwen3ForCausalLM.from_pretrained(qwen3_path, **kwargs)
1105
+ original_config = original.config
1106
+
1107
+ # Create AHA config
1108
+ config_dict = original_config.to_dict()
1109
+ config_dict["aha_window_size"] = aha_window_size
1110
+ config_dict["aha_lambda"] = aha_lambda
1111
+ config_dict["aha_distill_weight"] = aha_distill_weight
1112
+ config_dict["aha_ce_weight"] = aha_ce_weight
1113
+ config_dict["aha_gate_target"] = aha_gate_target
1114
+ config_dict["aha_reg_weight"] = aha_reg_weight
1115
+ config_dict["aha_force_gate_value"] = aha_force_gate_value
1116
+ config_dict["aha_mode"] = aha_mode
1117
+ config_dict["duo_sink_size"] = duo_sink_size
1118
+ config_dict["duo_recent_size"] = duo_recent_size
1119
+ config_dict["duo_alpha_init"] = duo_alpha_init
1120
+ config_dict["aha_local_kind"] = aha_local_kind
1121
+ config_dict["aha_router_granularity"] = aha_router_granularity
1122
+ config_dict["model_type"] = "aha_qwen3"
1123
+ aha_config = AHAQwen3Config(**config_dict)
1124
+ aha_config._attn_implementation = kwargs.get("attn_implementation", "sdpa")
1125
+
1126
+ # Create AHA model
1127
+ aha_model = cls(aha_config)
1128
+
1129
+ # Copy weights
1130
+ # embed_tokens, lm_head, norm
1131
+ aha_model.model.embed_tokens.load_state_dict(original.model.embed_tokens.state_dict())
1132
+ aha_model.lm_head.load_state_dict(original.lm_head.state_dict())
1133
+ aha_model.model.norm.load_state_dict(original.model.norm.state_dict())
1134
+ aha_model.model.rotary_emb.load_state_dict(original.model.rotary_emb.state_dict())
1135
+
1136
+ # Copy per-layer weights
1137
+ num_heads = original_config.num_attention_heads
1138
+ head_dim = aha_config.head_dim
1139
+
1140
+ for i in range(original_config.num_hidden_layers):
1141
+ orig_layer = original.model.layers[i]
1142
+ aha_layer = aha_model.model.layers[i]
1143
+
1144
+ # MLP + norms: direct copy
1145
+ aha_layer.mlp.load_state_dict(orig_layer.mlp.state_dict())
1146
+ aha_layer.input_layernorm.load_state_dict(orig_layer.input_layernorm.state_dict())
1147
+ aha_layer.post_attention_layernorm.load_state_dict(orig_layer.post_attention_layernorm.state_dict())
1148
+
1149
+ # Attention: k_proj, v_proj, o_proj, q_norm, k_norm — direct copy
1150
+ aha_attn = aha_layer.self_attn
1151
+ orig_attn = orig_layer.self_attn
1152
+ aha_attn.k_proj.load_state_dict(orig_attn.k_proj.state_dict())
1153
+ aha_attn.v_proj.load_state_dict(orig_attn.v_proj.state_dict())
1154
+ aha_attn.o_proj.load_state_dict(orig_attn.o_proj.state_dict())
1155
+ aha_attn.q_norm.load_state_dict(orig_attn.q_norm.state_dict())
1156
+ aha_attn.k_norm.load_state_dict(orig_attn.k_norm.state_dict())
1157
+
1158
+ # q_proj:
1159
+ # - dynamic / duo_dynamic mode: copy original Q rows, init extra
1160
+ # gate rows random (~0.5).
1161
+ # - duo mode: q_proj is identical shape to base Qwen3 (no gate
1162
+ # rows), so direct load_state_dict works.
1163
+ orig_q_weight = orig_attn.q_proj.weight.data # (num_heads*head_dim, hidden_size)
1164
+ aha_q_weight = aha_attn.q_proj.weight.data
1165
+ if aha_attn._has_dyn_gate_logits:
1166
+ aha_q_weight[:num_heads * head_dim, :] = orig_q_weight
1167
+ nn.init.normal_(aha_q_weight[num_heads * head_dim:, :], mean=0.0, std=0.01)
1168
+ if orig_attn.q_proj.bias is not None and aha_attn.q_proj.bias is not None:
1169
+ aha_attn.q_proj.bias.data[:num_heads * head_dim] = orig_attn.q_proj.bias.data
1170
+ nn.init.zeros_(aha_attn.q_proj.bias.data[num_heads * head_dim:])
1171
+ else:
1172
+ aha_attn.q_proj.load_state_dict(orig_attn.q_proj.state_dict())
1173
+
1174
+ del original
1175
+ return aha_model
1176
+
1177
+ @classmethod
1178
+ def from_pretrained_aha(cls, checkpoint_path: str, **kwargs):
1179
+ """Load an AHA-Qwen3 checkpoint saved by `trainer.save_model`.
1180
+
1181
+ Supports both single-file (``model.safetensors``) and multi-shard
1182
+ (``model.safetensors.index.json`` + ``model-*-of-*.safetensors``)
1183
+ layouts. The latter is automatically used by HF when a checkpoint
1184
+ exceeds the single-file size threshold (e.g. Qwen3-1.7B+).
1185
+ """
1186
+ config = AHAQwen3Config.from_pretrained(checkpoint_path)
1187
+ config._attn_implementation = kwargs.get(
1188
+ "attn_implementation",
1189
+ getattr(config, "_attn_implementation", "sdpa"),
1190
+ )
1191
+
1192
+ model = cls(config)
1193
+
1194
+ single_path = os.path.join(checkpoint_path, "model.safetensors")
1195
+ index_path = os.path.join(checkpoint_path, "model.safetensors.index.json")
1196
+ if os.path.exists(single_path):
1197
+ state_dict = load_file(single_path)
1198
+ elif os.path.exists(index_path):
1199
+ with open(index_path) as f:
1200
+ shard_index = json.load(f)
1201
+ shard_files = sorted(set(shard_index["weight_map"].values()))
1202
+ state_dict = {}
1203
+ for shard in shard_files:
1204
+ state_dict.update(load_file(os.path.join(checkpoint_path, shard)))
1205
+ else:
1206
+ raise FileNotFoundError(
1207
+ f"No safetensors file or index found in {checkpoint_path}. "
1208
+ f"Expected either model.safetensors or model.safetensors.index.json."
1209
+ )
1210
+ missing_keys, unexpected_keys = model.load_state_dict(state_dict, strict=False)
1211
+ allowed_missing = {"lm_head.weight"} if getattr(config, "tie_word_embeddings", False) else set()
1212
+ extra_missing = set(missing_keys) - allowed_missing
1213
+ if extra_missing or unexpected_keys:
1214
+ raise RuntimeError(
1215
+ f"Unexpected checkpoint mismatch. missing={sorted(extra_missing)}, "
1216
+ f"unexpected={sorted(unexpected_keys)}"
1217
+ )
1218
+ if "lm_head.weight" in missing_keys:
1219
+ model.tie_weights()
1220
+
1221
+ _apply_low_alpha_force_full_heads_from_env(model)
1222
+
1223
+ torch_dtype = kwargs.get("torch_dtype")
1224
+ if torch_dtype is not None:
1225
+ model = model.to(dtype=torch_dtype)
1226
+ return model
outputs/l2a_style/stage1/checkpoint-200/special_tokens_map.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>",
5
+ "<|object_ref_start|>",
6
+ "<|object_ref_end|>",
7
+ "<|box_start|>",
8
+ "<|box_end|>",
9
+ "<|quad_start|>",
10
+ "<|quad_end|>",
11
+ "<|vision_start|>",
12
+ "<|vision_end|>",
13
+ "<|vision_pad|>",
14
+ "<|image_pad|>",
15
+ "<|video_pad|>"
16
+ ],
17
+ "eos_token": {
18
+ "content": "<|im_end|>",
19
+ "lstrip": false,
20
+ "normalized": false,
21
+ "rstrip": false,
22
+ "single_word": false
23
+ },
24
+ "pad_token": {
25
+ "content": "<|endoftext|>",
26
+ "lstrip": false,
27
+ "normalized": false,
28
+ "rstrip": false,
29
+ "single_word": false
30
+ }
31
+ }
outputs/l2a_style/stage1/checkpoint-200/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
3
+ size 11422654
outputs/l2a_style/stage1/checkpoint-200/tokenizer_config.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>"
228
+ ],
229
+ "bos_token": null,
230
+ "clean_up_tokenization_spaces": false,
231
+ "eos_token": "<|im_end|>",
232
+ "errors": "replace",
233
+ "extra_special_tokens": {},
234
+ "model_max_length": 131072,
235
+ "pad_token": "<|endoftext|>",
236
+ "split_special_tokens": false,
237
+ "tokenizer_class": "Qwen2Tokenizer",
238
+ "unk_token": null
239
+ }
outputs/l2a_style/stage1/checkpoint-200/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
outputs/l2a_style/stage1/checkpoint-300/added_tokens.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|box_end|>": 151649,
9
+ "<|box_start|>": 151648,
10
+ "<|endoftext|>": 151643,
11
+ "<|file_sep|>": 151664,
12
+ "<|fim_middle|>": 151660,
13
+ "<|fim_pad|>": 151662,
14
+ "<|fim_prefix|>": 151659,
15
+ "<|fim_suffix|>": 151661,
16
+ "<|im_end|>": 151645,
17
+ "<|im_start|>": 151644,
18
+ "<|image_pad|>": 151655,
19
+ "<|object_ref_end|>": 151647,
20
+ "<|object_ref_start|>": 151646,
21
+ "<|quad_end|>": 151651,
22
+ "<|quad_start|>": 151650,
23
+ "<|repo_name|>": 151663,
24
+ "<|video_pad|>": 151656,
25
+ "<|vision_end|>": 151653,
26
+ "<|vision_pad|>": 151654,
27
+ "<|vision_start|>": 151652
28
+ }
outputs/l2a_style/stage1/checkpoint-300/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 %}
outputs/l2a_style/stage1/checkpoint-300/config.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_ce_weight": 0.0,
3
+ "aha_distill_weight": 0.0,
4
+ "aha_force_gate_value": null,
5
+ "aha_gate_init_full_prob": 0.9,
6
+ "aha_gate_target": 0.7,
7
+ "aha_hotstart_source": "/workspace",
8
+ "aha_lambda": 0.0003,
9
+ "aha_local_kind": "sink_recent",
10
+ "aha_mode": "dynamic",
11
+ "aha_reg_weight": -1.0,
12
+ "aha_router_granularity": "token",
13
+ "aha_window_size": 128,
14
+ "architectures": [
15
+ "AHAQwen3ForCausalLM"
16
+ ],
17
+ "attention_bias": true,
18
+ "attention_dropout": 0.0,
19
+ "auto_map": {
20
+ "AutoConfig": "modeling_aha_qwen3.AHAQwen3Config",
21
+ "AutoModelForCausalLM": "modeling_aha_qwen3.AHAQwen3ForCausalLM"
22
+ },
23
+ "bos_token_id": 151643,
24
+ "duo_alpha_init": 1.0,
25
+ "duo_recent_size": 256,
26
+ "duo_sink_size": 64,
27
+ "eos_token_id": 151645,
28
+ "head_dim": 128,
29
+ "hidden_act": "silu",
30
+ "hidden_size": 2048,
31
+ "initializer_range": 0.02,
32
+ "intermediate_size": 6144,
33
+ "layer_types": [
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
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention",
57
+ "full_attention",
58
+ "full_attention",
59
+ "full_attention",
60
+ "full_attention",
61
+ "full_attention"
62
+ ],
63
+ "max_position_embeddings": 40960,
64
+ "max_window_layers": 28,
65
+ "model_type": "aha_qwen3",
66
+ "num_attention_heads": 16,
67
+ "num_hidden_layers": 28,
68
+ "num_key_value_heads": 8,
69
+ "rms_norm_eps": 1e-06,
70
+ "rope_scaling": null,
71
+ "rope_theta": 1000000,
72
+ "sliding_window": null,
73
+ "tie_word_embeddings": false,
74
+ "torch_dtype": "bfloat16",
75
+ "transformers_version": "4.54.0",
76
+ "use_cache": false,
77
+ "use_sliding_window": false,
78
+ "vocab_size": 151936
79
+ }
outputs/l2a_style/stage1/checkpoint-300/dynamic_duo_state.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 300,
3
+ "router_granularity": "token",
4
+ "native_gate_rows_per_layer": 1,
5
+ "effective_sparsity_denominator": "token x KV-head x layer",
6
+ "gate_soft_mean": 0.6445921063423157,
7
+ "gate_soft_std": 0.19594143331050873,
8
+ "gate_hard_mean": 0.7741450071334839,
9
+ "gate_min": 0.0028076171875,
10
+ "gate_max": 0.94921875,
11
+ "gate_weight_l2": 0.8540672063827515,
12
+ "gate_weight_abs_mean": 0.002572857541963458,
13
+ "gate_bias_mean": 2.203125,
14
+ "gate_bias_std": 0.0,
15
+ "gate_bias_alpha_mean": 0.9005298018455505,
16
+ "gate_bias_alpha_gt05": 1.0
17
+ }
outputs/l2a_style/stage1/checkpoint-300/dynamic_duo_train_args.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aha_checkpoint": "/workspace/outputs/l2a_style/hotstart",
3
+ "model_path": "/workspace",
4
+ "output_dir": "/workspace/outputs/l2a_style/stage1",
5
+ "am_dataset_path": "/workspace/recipe/data/am_distilled_long_mix",
6
+ "am_dataset_split": "train",
7
+ "data_source": "am_distilled",
8
+ "am_label_mode": "full",
9
+ "longbench_tasks": [
10
+ "passage_retrieval_en",
11
+ "multifieldqa_en",
12
+ "qasper",
13
+ "2wikimqa"
14
+ ],
15
+ "longbench_samples_per_task": 30,
16
+ "longbench_cache_dir": "/workspace/AHA/AHA-Qwen3/data/longbench_cache",
17
+ "max_length": 8192,
18
+ "num_steps": 300,
19
+ "warmup_ratio": 0.1,
20
+ "lr": 3e-05,
21
+ "reg_weight": 0.1,
22
+ "reg_head_weight_mode": "uniform",
23
+ "reg_head_weight_power": 1.0,
24
+ "ce_weight": 0.0,
25
+ "distill_tail_frac": 0.0,
26
+ "distill_tail_weight": 0.0,
27
+ "batch_size": 1,
28
+ "grad_accum": 1,
29
+ "save_steps": 100,
30
+ "log_steps": 10,
31
+ "seed": 42,
32
+ "dtype": "bfloat16",
33
+ "attn_impl": "sdpa",
34
+ "aha_local_kind": "sink_recent",
35
+ "router_granularity": "token"
36
+ }