tarruda commited on
Commit
affd92d
·
0 Parent(s):

Super-squash branch 'main' using huggingface_hub

Browse files
.gitattributes ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00001-of-00004.gguf filter=lfs diff=lfs merge=lfs -text
37
+ IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00002-of-00004.gguf filter=lfs diff=lfs merge=lfs -text
38
+ IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00003-of-00004.gguf filter=lfs diff=lfs merge=lfs -text
39
+ IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00004-of-00004.gguf filter=lfs diff=lfs merge=lfs -text
40
+ Nex-N2-Pro-mmproj-BF16.gguf filter=lfs diff=lfs merge=lfs -text
41
+ Nex-N2-Pro-mmproj-F16.gguf filter=lfs diff=lfs merge=lfs -text
42
+ Nex-N2-Pro-mmproj-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
43
+ imatrix.gguf filter=lfs diff=lfs merge=lfs -text
IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00001-of-00004.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:241d3c961453bd38a44d8acca7f5740371fb49071e75e6f3af9e859ee22e8226
3
+ size 10942829
IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00002-of-00004.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:572ed56a257ad1da397e6dd5e38686d2374fb36934354c73d57d7db9fd123dff
3
+ size 49672553216
IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00003-of-00004.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0310f5f0c1b418510e2ee43660320fc4a9d5aa6ae254ebe5291552110bb75ce1
3
+ size 49954145792
IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00004-of-00004.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:069b6d33eec17a7e9c83cb67a685509c5079ba71022f20c6ef79977db7f4725f
3
+ size 25251431296
Nex-N2-Pro-mmproj-BF16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ab9cfa2180f6c21fe3aaec7710f90847862e3867cf3644bcc03790d60d444043
3
+ size 921704480
Nex-N2-Pro-mmproj-F16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f083cfac3d96f273b6359f16d9d9044510439113cfe2384085638f9df8b6d590
3
+ size 918165536
Nex-N2-Pro-mmproj-Q8_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1b99dbe04a91f7f8828bf2d9af2e62e4e531019637838c7da85e5a09e8b3f02b
3
+ size 624229280
README.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ quantized_by: tarruda
3
+ pipeline_tag: image-text-to-text
4
+ base_model: nex-agi/Nex-N2-Pro
5
+ base_model_relation: quantized
6
+ license: apache-2.0
7
+ ---
8
+
9
+ # Nex N2 Pro GGUF
10
+
11
+ My custom IQ2_XXS/IQ3_XXS GGUF quantization for
12
+ [nex-agi/Nex-N2-Pro](https://huggingface.co/nex-agi/Nex-N2-Pro)
13
+
14
+ The chat template adds `preserve_thinking` option to improve experience with
15
+ coding agents on platforms with slow prompt processing.
16
+
17
+ ## Quant Recipes
18
+
19
+ | Recipe | Quant Size | Default type | Tensor-specific overrides |
20
+ | --- | --- | --- | --- |
21
+ | `IQ3_XXS` | 119093.00 MiB (2.52 BPW) | `Q6_K` | `*.attn*=q8_0`, `ffn_down_exps=iq3_xxs`, `ffn_gate_exps=iq2_xxs`, `ffn_up_exps=iq2_xxs` |
22
+
23
+ ## Usage
24
+
25
+ Here's an example script:
26
+
27
+ ```bash
28
+ #!/bin/sh -e
29
+ model=./IQ3_XXS/Nex-N2-Pro-IQ3_XXS-00001-of-00004.gguf
30
+ mmproj=./IQ3_XXS/Nex-N2-Pro-mmproj-Q8_0.gguf
31
+
32
+ parallel=1
33
+ ctx=262144
34
+
35
+ reasoning_budget_message=". Need answer.
36
+ Let's draft final."
37
+
38
+ ctx_size=$((ctx * parallel))
39
+ llama-server --no-warmup --no-mmap \
40
+ --model $model --mmproj $mmproj \
41
+ -ctk q8_0 -ctv q8_0 \
42
+ --ctx-size $ctx_size -np $parallel \
43
+ --temp 0.7 --top-p 0.95 --top-k 40 \
44
+ --repeat-penalty 1.0 --presence-penalty 0.0 \
45
+ -cram 0 \
46
+ --reasoning-budget-message "$reasoning_budget_message" \
47
+ --reasoning-preserve \
48
+ --ctx-checkpoints 2 --checkpoint-min-step 1024 \
49
+ --host 0.0.0.0
50
+ ```
chat_template.jinja ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- if tools and tools is iterable and tools is not mapping %}
46
+ {{- '<|im_start|>system\n' }}
47
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
48
+ {%- for tool in tools %}
49
+ {{- "\n" }}
50
+ {{- tool | tojson }}
51
+ {%- endfor %}
52
+ {{- "\n</tools>" }}
53
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
54
+ {%- if messages[0].role == 'system' %}
55
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
56
+ {%- if content %}
57
+ {{- '\n\n' + content }}
58
+ {%- endif %}
59
+ {%- endif %}
60
+ {{- '<|im_end|>\n' }}
61
+ {%- else %}
62
+ {%- if messages[0].role == 'system' %}
63
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
64
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
65
+ {%- endif %}
66
+ {%- endif %}
67
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
68
+ {%- for message in messages[::-1] %}
69
+ {%- set index = (messages|length - 1) - loop.index0 %}
70
+ {%- if ns.multi_step_tool and message.role == "user" %}
71
+ {%- set content = render_content(message.content, false)|trim %}
72
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
73
+ {%- set ns.multi_step_tool = false %}
74
+ {%- set ns.last_query_index = index %}
75
+ {%- endif %}
76
+ {%- endif %}
77
+ {%- endfor %}
78
+ {%- if ns.multi_step_tool %}
79
+ {{- raise_exception('No user query found in messages.') }}
80
+ {%- endif %}
81
+ {%- for message in messages %}
82
+ {%- set content = render_content(message.content, true)|trim %}
83
+ {%- if message.role == "system" %}
84
+ {%- if not loop.first %}
85
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
86
+ {%- endif %}
87
+ {%- elif message.role == "user" %}
88
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
89
+ {%- elif message.role == "assistant" %}
90
+ {%- set reasoning_content = '' %}
91
+ {%- if message.reasoning_content is string %}
92
+ {%- set reasoning_content = message.reasoning_content %}
93
+ {%- else %}
94
+ {%- if '</think>' in content %}
95
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
96
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
97
+ {%- endif %}
98
+ {%- endif %}
99
+ {%- set reasoning_content = reasoning_content|trim %}
100
+ {%- if reasoning_content %}
101
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
102
+ {%- else %}
103
+ {{- '<|im_start|>' + message.role + '\n' + content }}
104
+ {%- endif %}
105
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
106
+ {%- for tool_call in message.tool_calls %}
107
+ {%- if tool_call.function is defined %}
108
+ {%- set tool_call = tool_call.function %}
109
+ {%- endif %}
110
+ {%- if loop.first %}
111
+ {%- if content|trim %}
112
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
113
+ {%- else %}
114
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
115
+ {%- endif %}
116
+ {%- else %}
117
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
118
+ {%- endif %}
119
+ {%- if tool_call.arguments is defined %}
120
+ {%- for args_name, args_value in tool_call.arguments|items %}
121
+ {{- '<parameter=' + args_name + '>\n' }}
122
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
123
+ {{- args_value }}
124
+ {{- '\n</parameter>\n' }}
125
+ {%- endfor %}
126
+ {%- endif %}
127
+ {{- '</function>\n</tool_call>' }}
128
+ {%- endfor %}
129
+ {%- endif %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif message.role == "tool" %}
132
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
133
+ {{- '<|im_start|>user' }}
134
+ {%- endif %}
135
+ {{- '\n<tool_response>\n' }}
136
+ {{- content }}
137
+ {{- '\n</tool_response>' }}
138
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
139
+ {{- '<|im_end|>\n' }}
140
+ {%- elif loop.last %}
141
+ {{- '<|im_end|>\n' }}
142
+ {%- endif %}
143
+ {%- else %}
144
+ {{- raise_exception('Unexpected message role.') }}
145
+ {%- endif %}
146
+ {%- endfor %}
147
+ {%- if add_generation_prompt %}
148
+ {{- '<|im_start|>assistant\n' }}
149
+ {%- if enable_thinking is defined and enable_thinking is false %}
150
+ {{- '<think>\n\n</think>\n\n' }}
151
+ {%- else %}
152
+ {{- '<think>' }}
153
+ {%- endif %}
154
+ {%- endif %}
imatrix.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:529343f77da6bcdecba0f22ae84c99c4794f89e6b710d5ea3296319ae5f13239
3
+ size 1142781952
scripts/convert-to-gguf.sh ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ # Validate that exactly 2 arguments are provided
6
+ if [ $# -ne 2 ]; then
7
+ echo "Error: Exactly 2 arguments required."
8
+ echo "Usage: $0 <llama_cpp_dir> <original_safetensors_path>"
9
+ echo "Example: $0 ~/code/llama.cpp ../../Qwen/Qwen3.5-397B-A10B"
10
+ exit 1
11
+ fi
12
+
13
+ # Assign arguments to variables for clarity
14
+ LLAMA_CPP_DIR="$1"
15
+ ORIGINAL_SAFETENSORS_PATH="$2"
16
+ CONVERSION_MODEL_PATH="$ORIGINAL_SAFETENSORS_PATH"
17
+
18
+ # Validate that the llama.cpp directory exists
19
+ if [ ! -d "$LLAMA_CPP_DIR" ]; then
20
+ echo "Error: llama.cpp directory not found: $LLAMA_CPP_DIR"
21
+ exit 1
22
+ fi
23
+
24
+ # Construct the path to the conversion script
25
+ CONVERT_TO_GGUF_PATH="$LLAMA_CPP_DIR/convert_hf_to_gguf.py"
26
+
27
+ # Validate that the Python script exists
28
+ if [ ! -f "$CONVERT_TO_GGUF_PATH" ]; then
29
+ echo "Error: Python script not found: $CONVERT_TO_GGUF_PATH"
30
+ exit 1
31
+ fi
32
+
33
+ # Validate that the model path exists
34
+ if [ ! -e "$ORIGINAL_SAFETENSORS_PATH" ]; then
35
+ echo "Error: Model path not found: $ORIGINAL_SAFETENSORS_PATH"
36
+ exit 1
37
+ fi
38
+
39
+ ORIGINAL_SAFETENSORS_PATH="$(cd "$(dirname "$ORIGINAL_SAFETENSORS_PATH")" && pwd)/$(basename "$ORIGINAL_SAFETENSORS_PATH")"
40
+ MODEL_NAME="$(basename "$ORIGINAL_SAFETENSORS_PATH")"
41
+ # Get the directory where the script is located
42
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
+ TOKENIZER_CONFIG_PATCH="$SCRIPT_DIR/tokenizer_config.patch"
44
+
45
+ # Resolve GGUF_OUT_DIR absolutely (parent of script directory)
46
+ # We cd into the parent and run pwd to get the absolute path
47
+ if [ ! -d "$SCRIPT_DIR/.." ]; then
48
+ echo "Error: Could not resolve parent directory for output."
49
+ exit 1
50
+ fi
51
+ GGUF_OUT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
52
+ OVERLAY_PARENT="$GGUF_OUT_DIR/.conversion-model"
53
+ OVERLAY_MODEL_PATH="$OVERLAY_PARENT/$MODEL_NAME"
54
+
55
+ # Define BF16_DIR inside GGUF_OUT_DIR
56
+ BF16_DIR="$GGUF_OUT_DIR/BF16"
57
+ mkdir -pv "$BF16_DIR"
58
+
59
+ # Activate virtual environment inside llama.cpp directory
60
+ cd "$LLAMA_CPP_DIR"
61
+ source .venv/bin/activate
62
+ cd -
63
+
64
+ rm -rf "$OVERLAY_PARENT"
65
+ mkdir -p "$OVERLAY_MODEL_PATH"
66
+ for model_file in "$ORIGINAL_SAFETENSORS_PATH"/* "$ORIGINAL_SAFETENSORS_PATH"/.[!.]*; do
67
+ [ -e "$model_file" ] || continue
68
+ [ "$(basename "$model_file")" = "tokenizer_config.json" ] && continue
69
+ ln -s "$model_file" "$OVERLAY_MODEL_PATH/"
70
+ done
71
+ cp "$ORIGINAL_SAFETENSORS_PATH/tokenizer_config.json" "$OVERLAY_MODEL_PATH/tokenizer_config.json"
72
+ patch "$OVERLAY_MODEL_PATH/tokenizer_config.json" "$TOKENIZER_CONFIG_PATCH"
73
+ CONVERSION_MODEL_PATH="$OVERLAY_MODEL_PATH"
74
+
75
+ # Execute the Python script with the provided arguments
76
+ # Main conversion output goes to BF16_DIR
77
+
78
+ "$CONVERT_TO_GGUF_PATH" --no-mtp --no-tensor-first-split --split-max-size 50G --outfile "$BF16_DIR/${MODEL_NAME}-BF16.gguf" "$CONVERSION_MODEL_PATH"
79
+
80
+ # # mmproj output goes to GGUF_OUT_DIR
81
+ "$CONVERT_TO_GGUF_PATH" --mmproj --outtype bf16 --outfile "$GGUF_OUT_DIR/${MODEL_NAME}-mmproj-BF16.gguf" "$CONVERSION_MODEL_PATH"
82
+ # # also do FP16 and Q8_0 versions
83
+ "$CONVERT_TO_GGUF_PATH" --mmproj --outtype f16 --outfile "$GGUF_OUT_DIR/${MODEL_NAME}-mmproj-F16.gguf" "$CONVERSION_MODEL_PATH"
84
+ "$CONVERT_TO_GGUF_PATH" --mmproj --outtype q8_0 --outfile "$GGUF_OUT_DIR/${MODEL_NAME}-mmproj-Q8_0.gguf" "$CONVERSION_MODEL_PATH"
85
+
86
+ rm -rf "$OVERLAY_PARENT"
scripts/generate-imatrix.sh ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ # Validate that exactly 2 arguments are provided
6
+ if [ $# -ne 2 ]; then
7
+ echo "Error: Exactly 2 arguments required."
8
+ echo "Usage: $0 <llama_cpp_dir> <gguf>"
9
+ echo "Example: $0 ~/code/llama.cpp BF16/Qwen3.5-35B-A3B-BF16-00001-of-00003.gguf"
10
+ exit 1
11
+ fi
12
+
13
+ # Assign arguments to variables for clarity
14
+ LLAMA_CPP_DIR="$1"
15
+ GGUF_PATH="$2"
16
+
17
+ # Validate that the llama.cpp directory exists
18
+ if [ ! -d "$LLAMA_CPP_DIR" ]; then
19
+ echo "Error: llama.cpp directory not found: $LLAMA_CPP_DIR"
20
+ exit 1
21
+ fi
22
+
23
+
24
+ # Validate that the Python script exists
25
+ if [ ! -e "$GGUF_PATH" ]; then
26
+ echo "Error: GGUF not found: $GGUF_PATH"
27
+ exit 1
28
+ fi
29
+
30
+ CALIBRATION_DATASET_PATH=$HOME/calibration-data.txt
31
+
32
+ if [ ! -e $CALIBRATION_DATASET_PATH ]; then
33
+ # download the calibration dataset
34
+ curl -L https://gist.githubusercontent.com/ubergarm/edfeb3ff9c6ec8b49e88cdf627b0711a/raw/ba5b01b6960a86874592f5913e283746ff734483/ubergarm-imatrix-calibration-corpus-v02.txt > $CALIBRATION_DATASET_PATH
35
+ fi
36
+
37
+ # Get the directory where the script is located
38
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
39
+ IMATRIX_OUT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
40
+ IMATRIX_PATH="$IMATRIX_OUT_DIR/imatrix.gguf"
41
+
42
+ if [ -e "$IMATRIX_PATH" ]; then
43
+ echo "Error: imatrix already exists: $IMATRIX_PATH"
44
+ exit 1
45
+ fi
46
+
47
+ $LLAMA_CPP_DIR/build/bin/llama-imatrix \
48
+ -fit off \
49
+ --model "$GGUF_PATH"\
50
+ -f $CALIBRATION_DATASET_PATH \
51
+ -o $IMATRIX_PATH \
52
+ --ctx-size 512 \
53
+ -ub 4096 -b 4096 \
54
+ --no-mmap
scripts/quantize.sh ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+ shopt -s extglob
5
+
6
+ common_tensor_types="
7
+ "
8
+
9
+ TYPE_DEFAULT_FALLBACK=Q8_0
10
+
11
+ recipes=(
12
+ "
13
+ RECIPE_NAME=IQ3_XXS
14
+ TYPE_DEFAULT=q6_k
15
+
16
+ ^token_embd[.]weight$=q4_k
17
+ blk\..*\.attn_q.*=q8_0
18
+ blk\..*\.attn_k.*=q8_0
19
+ blk\..*\.attn_v.*=q8_0
20
+ blk\..*\.attn_output.*=q8_0
21
+ ffn_down_exps=iq3_xxs
22
+ ffn_gate_exps=iq2_xxs
23
+ ffn_up_exps=iq2_xxs
24
+ "
25
+ )
26
+
27
+ recipe_line() {
28
+ local line="$1"
29
+ printf '%s' "${line##+([[:space:]])}"
30
+ }
31
+
32
+ recipe_value() {
33
+ local recipe="$1"
34
+ local key="$2"
35
+ local line
36
+
37
+ while IFS= read -r line || [ -n "$line" ]; do
38
+ line="$(recipe_line "$line")"
39
+ case "$line" in
40
+ "${key}="*)
41
+ printf '%s' "${line#*=}"
42
+ return 0
43
+ ;;
44
+ esac
45
+ done <<<"$recipe"
46
+
47
+ return 1
48
+ }
49
+
50
+ recipe_type_default() {
51
+ local recipe="$1"
52
+
53
+ recipe_value "$recipe" TYPE_DEFAULT || printf '%s' "$TYPE_DEFAULT_FALLBACK"
54
+ }
55
+
56
+ print_recipes() {
57
+ local recipe recipe_name type_default
58
+
59
+ for recipe in "${recipes[@]}"; do
60
+ recipe_name="$(recipe_value "$recipe" RECIPE_NAME)"
61
+ type_default="$(recipe_type_default "$recipe")"
62
+
63
+ printf ' %s:\n' "$recipe_name"
64
+ printf ' TYPE_DEFAULT=%s\n' "$type_default"
65
+ printf '\n'
66
+ done
67
+ }
68
+
69
+ SELECTED_RECIPE_NAME=
70
+ SELECTED_TYPE_DEFAULT=
71
+ SELECTED_RECIPE=
72
+
73
+ load_recipe() {
74
+ local requested_recipe="$1"
75
+ local recipe recipe_name
76
+
77
+ for recipe in "${recipes[@]}"; do
78
+ recipe_name="$(recipe_value "$recipe" RECIPE_NAME)"
79
+ if [ "$recipe_name" = "$requested_recipe" ]; then
80
+ SELECTED_RECIPE_NAME="$recipe_name"
81
+ SELECTED_TYPE_DEFAULT="$(recipe_type_default "$recipe")"
82
+ SELECTED_RECIPE="$recipe"
83
+ return 0
84
+ fi
85
+ done
86
+
87
+ return 1
88
+ }
89
+
90
+ append_tensor_type_lines() {
91
+ local block="$1"
92
+ local output_path="$2"
93
+ local skip_recipe_metadata="${3:-false}"
94
+ local line
95
+
96
+ while IFS= read -r line || [ -n "$line" ]; do
97
+ line="$(recipe_line "$line")"
98
+ if [ "$skip_recipe_metadata" = true ]; then
99
+ case "$line" in
100
+ RECIPE_NAME=* | TYPE_DEFAULT=*)
101
+ continue
102
+ ;;
103
+ esac
104
+ fi
105
+
106
+ if [ -z "$line" ]; then
107
+ continue
108
+ fi
109
+
110
+ printf '%s\n' "$line" >>"$output_path"
111
+ done <<<"$block"
112
+ }
113
+
114
+ write_tensor_type_file() {
115
+ local recipe="$1"
116
+ local output_path="$2"
117
+
118
+ : >"$output_path"
119
+ append_tensor_type_lines "$common_tensor_types" "$output_path"
120
+ append_tensor_type_lines "$recipe" "$output_path" true
121
+ }
122
+
123
+ usage() {
124
+ cat <<'EOF'
125
+ Usage: quantize.sh <llama_cpp_dir> <recipe_name> [--dry-run] [--split]
126
+
127
+ Outputs are written relative to the current directory.
128
+
129
+ Environment overrides:
130
+ INPUT_GGUF=<path> Input GGUF. Defaults to the first *BF16*.gguf in BF16/.
131
+ IMATRIX_PATH=<path> Importance matrix path. Defaults to imatrix.gguf.
132
+
133
+ Available recipes:
134
+ EOF
135
+ print_recipes
136
+ cat <<'EOF'
137
+ Examples:
138
+ ./scripts/quantize.sh ~/code/llama.cpp IQ3_XXS --dry-run
139
+ ./scripts/quantize.sh ~/code/llama.cpp IQ3_XXS
140
+ ./scripts/quantize.sh ~/code/llama.cpp IQ3_XXS --split
141
+ EOF
142
+ }
143
+
144
+ if [ $# -lt 2 ]; then
145
+ usage
146
+ exit 1
147
+ fi
148
+
149
+ LLAMA_CPP_DIR="$1"
150
+ REQUESTED_RECIPE="$2"
151
+ DRY_RUN=false
152
+ SPLIT=false
153
+ shift 2
154
+
155
+ while [ $# -gt 0 ]; do
156
+ case "$1" in
157
+ --dry-run)
158
+ DRY_RUN=true
159
+ ;;
160
+ --split)
161
+ SPLIT=true
162
+ ;;
163
+ *)
164
+ usage
165
+ exit 1
166
+ ;;
167
+ esac
168
+ shift
169
+ done
170
+
171
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
172
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
173
+ BF16_DIR="$PROJECT_DIR/BF16"
174
+ SPLIT_SCRIPT="$SCRIPT_DIR/split.sh"
175
+
176
+ if ! load_recipe "$REQUESTED_RECIPE"; then
177
+ echo "Error: unknown recipe: $REQUESTED_RECIPE" >&2
178
+ usage >&2
179
+ exit 1
180
+ fi
181
+
182
+ if [ ! -d "$LLAMA_CPP_DIR" ]; then
183
+ echo "Error: llama.cpp directory not found: $LLAMA_CPP_DIR" >&2
184
+ exit 1
185
+ fi
186
+
187
+ QUANTIZE_BIN="$LLAMA_CPP_DIR/build/bin/llama-quantize"
188
+ SPLIT_BIN="$LLAMA_CPP_DIR/build/bin/llama-gguf-split"
189
+
190
+ if [ ! -x "$QUANTIZE_BIN" ]; then
191
+ echo "Error: llama-quantize binary not found: $QUANTIZE_BIN" >&2
192
+ exit 1
193
+ fi
194
+
195
+ if [ "$SPLIT" = true ] && [ ! -x "$SPLIT_SCRIPT" ]; then
196
+ echo "Error: split script not found or not executable: $SPLIT_SCRIPT" >&2
197
+ exit 1
198
+ fi
199
+
200
+ if [ "$SPLIT" = true ] && [ ! -x "$SPLIT_BIN" ]; then
201
+ echo "Error: llama-gguf-split binary not found: $SPLIT_BIN" >&2
202
+ exit 1
203
+ fi
204
+
205
+ if [ -z "${INPUT_GGUF-}" ]; then
206
+ if [ ! -d "$BF16_DIR" ]; then
207
+ echo "Error: BF16 directory not found: $BF16_DIR" >&2
208
+ exit 1
209
+ fi
210
+
211
+ INPUT_GGUF="$(find "$BF16_DIR" -maxdepth 1 -name "*BF16*.gguf" -type f | sort | head -n 1)"
212
+ fi
213
+
214
+ if [ -z "$INPUT_GGUF" ] || [ ! -e "$INPUT_GGUF" ]; then
215
+ echo "Error: input GGUF not found: ${INPUT_GGUF:-<none>}" >&2
216
+ exit 1
217
+ fi
218
+
219
+ IMATRIX_PATH="${IMATRIX_PATH:-$PROJECT_DIR/imatrix.gguf}"
220
+ if [ ! -e "$IMATRIX_PATH" ]; then
221
+ echo "Error: imatrix file not found: $IMATRIX_PATH" >&2
222
+ exit 1
223
+ fi
224
+
225
+ cd "$LLAMA_CPP_DIR"
226
+ if [ -f .venv/bin/activate ]; then
227
+ # shellcheck disable=SC1091
228
+ source .venv/bin/activate
229
+ fi
230
+ cd - >/dev/null
231
+
232
+ OUTPUT_BASE_DIR="$(pwd)"
233
+ INPUT_BASENAME="$(basename "$INPUT_GGUF")"
234
+ MODEL_NAME="$(printf '%s\n' "$INPUT_BASENAME" | sed -E 's/-BF16(-[0-9]+-of-[0-9]+)?\.gguf$//')"
235
+ INTERMEDIATE_OUTPUT="$OUTPUT_BASE_DIR/${MODEL_NAME}-${SELECTED_RECIPE_NAME}.gguf"
236
+ OUTPUT_DIR="$OUTPUT_BASE_DIR/$SELECTED_RECIPE_NAME"
237
+
238
+ if [ "$DRY_RUN" = false ] && [ -e "$INTERMEDIATE_OUTPUT" ]; then
239
+ echo "Error: quantized output already exists: $INTERMEDIATE_OUTPUT" >&2
240
+ exit 1
241
+ fi
242
+
243
+ TENSOR_TYPE_FILE="$(mktemp "${TMPDIR:-/tmp}/nex-n2-pro-tensor-types.XXXXXX")"
244
+ cleanup() {
245
+ rm -f "$TENSOR_TYPE_FILE"
246
+ }
247
+ trap cleanup EXIT
248
+
249
+ write_tensor_type_file "$SELECTED_RECIPE" "$TENSOR_TYPE_FILE"
250
+
251
+ echo "Input: $INPUT_GGUF"
252
+ echo "Recipe name: $SELECTED_RECIPE_NAME"
253
+ echo "Tensor type file: $TENSOR_TYPE_FILE"
254
+ echo "Default type: $SELECTED_TYPE_DEFAULT"
255
+ echo "imatrix: $IMATRIX_PATH"
256
+ echo "Output base: $OUTPUT_BASE_DIR"
257
+ echo "Split output: $SPLIT"
258
+
259
+ if [ "$DRY_RUN" = true ]; then
260
+ "$QUANTIZE_BIN" \
261
+ --dry-run \
262
+ --allow-requantize \
263
+ --tensor-type-file "$TENSOR_TYPE_FILE" \
264
+ --imatrix "$IMATRIX_PATH" \
265
+ "$INPUT_GGUF" \
266
+ "$INTERMEDIATE_OUTPUT" \
267
+ "$SELECTED_TYPE_DEFAULT"
268
+ else
269
+ "$QUANTIZE_BIN" \
270
+ --allow-requantize \
271
+ --tensor-type-file "$TENSOR_TYPE_FILE" \
272
+ --imatrix "$IMATRIX_PATH" \
273
+ "$INPUT_GGUF" \
274
+ "$INTERMEDIATE_OUTPUT" \
275
+ "$SELECTED_TYPE_DEFAULT"
276
+ fi
277
+
278
+ if [ "$DRY_RUN" = false ]; then
279
+ if [ "$SPLIT" = true ]; then
280
+ "$SPLIT_SCRIPT" \
281
+ "$LLAMA_CPP_DIR" \
282
+ "$INTERMEDIATE_OUTPUT" \
283
+ "$OUTPUT_DIR" \
284
+ "${MODEL_NAME}-${SELECTED_RECIPE_NAME}" \
285
+ --remove-input
286
+ else
287
+ echo "Quantization complete. Output saved to: $INTERMEDIATE_OUTPUT"
288
+ fi
289
+ fi
scripts/report-imatrix-activations.py ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Report FFN activation importance from a llama.cpp imatrix GGUF.
3
+
4
+ The script ranks FFN down and gate/up activation streams from imatrix tensors.
5
+ For MoE tensors, it also tries to infer the routed expert count and report the
6
+ highest-activation block/expert pairs.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import math
13
+ import re
14
+ import sys
15
+ from collections import Counter, defaultdict
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Iterable
19
+
20
+ import numpy as np
21
+
22
+ try:
23
+ from gguf import GGUFReader
24
+ except ImportError as exc: # pragma: no cover - exercised by users without gguf.
25
+ raise SystemExit(
26
+ "Could not import gguf. Run this with a Python environment that has "
27
+ "llama.cpp's gguf package installed, for example:\n"
28
+ " ~/code/llama.cpp/.venv/bin/python scripts/report-imatrix-activations.py imatrix.gguf"
29
+ ) from exc
30
+
31
+
32
+ BLOCK_PATTERNS = (
33
+ re.compile(r"^blk\.(\d+)\.(.+)\.weight$"),
34
+ re.compile(r"^layers\.(\d+)\.(.+)\.weight$"),
35
+ re.compile(r"^model\.layers\.(\d+)\.(.+)\.weight$"),
36
+ )
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Candidate:
41
+ base: str
42
+ tensor_name: str
43
+ count_name: str
44
+ block: int | None
45
+ module: str
46
+ kind: str
47
+ projection: str
48
+ family: str
49
+ n: int
50
+
51
+
52
+ @dataclass
53
+ class Row:
54
+ block: int | None
55
+ family: str
56
+ kind: str
57
+ projection: str
58
+ tensor: str
59
+ count: float
60
+ n: int
61
+ mean_mse: float
62
+ rms: float
63
+ p95: float
64
+ p99: float
65
+ max_value: float
66
+ gt1: float
67
+ gt4: float
68
+
69
+
70
+ @dataclass
71
+ class ExpertRow:
72
+ block: int
73
+ expert: int
74
+ family: str
75
+ tensor: str
76
+ count: float
77
+ n: int
78
+ mean_mse: float
79
+ rms: float
80
+ p95: float
81
+ p99: float
82
+ max_value: float
83
+
84
+
85
+ def parse_args() -> argparse.Namespace:
86
+ parser = argparse.ArgumentParser(
87
+ description="Rank FFN down and gate/up activation streams from an imatrix GGUF.",
88
+ )
89
+ parser.add_argument("imatrix", type=Path, help="Path to an imatrix .gguf file")
90
+ parser.add_argument(
91
+ "--top",
92
+ type=int,
93
+ default=15,
94
+ help="Rows to show in the top tensor-stream table. Default: 15",
95
+ )
96
+ parser.add_argument(
97
+ "--family-top",
98
+ type=int,
99
+ default=5,
100
+ help="Rows to show per tensor family. Default: 5",
101
+ )
102
+ parser.add_argument(
103
+ "--top-experts",
104
+ type=int,
105
+ default=25,
106
+ help="Rows to show in the combined routed expert table. Default: 25",
107
+ )
108
+ parser.add_argument(
109
+ "--top-expert-streams",
110
+ type=int,
111
+ default=15,
112
+ help="Rows to show in the per-stream routed expert table. Default: 15",
113
+ )
114
+ parser.add_argument(
115
+ "--last-blocks",
116
+ type=int,
117
+ default=10,
118
+ help="Number of final MoE blocks to show in the compact table. Default: 10",
119
+ )
120
+ parser.add_argument(
121
+ "--experts",
122
+ type=int,
123
+ default=None,
124
+ help="Override routed expert count. Useful when it cannot be inferred.",
125
+ )
126
+ parser.add_argument(
127
+ "--no-experts",
128
+ action="store_true",
129
+ help="Skip per-expert routed rankings.",
130
+ )
131
+ parser.add_argument(
132
+ "--include-router-input",
133
+ action="store_true",
134
+ help="Include ffn_gate_inp/router input tensors when present.",
135
+ )
136
+ return parser.parse_args()
137
+
138
+
139
+ def field_type_name(field: object, index: int) -> str:
140
+ try:
141
+ return field.types[index].name
142
+ except Exception:
143
+ return ""
144
+
145
+
146
+ def part_to_list(part: object) -> object:
147
+ return part.tolist() if hasattr(part, "tolist") else part
148
+
149
+
150
+ def decode_bytes(values: object) -> str:
151
+ if isinstance(values, list):
152
+ return bytes(values).decode("utf-8", errors="replace")
153
+ if isinstance(values, np.ndarray):
154
+ return bytes(values.tolist()).decode("utf-8", errors="replace")
155
+ if isinstance(values, bytes):
156
+ return values.decode("utf-8", errors="replace")
157
+ return str(values)
158
+
159
+
160
+ def decode_field(field: object) -> object:
161
+ """Best-effort conversion of a GGUF ReaderField into a printable value."""
162
+ if not getattr(field, "parts", None):
163
+ return ""
164
+
165
+ if field_type_name(field, 0) == "STRING":
166
+ return decode_bytes(part_to_list(field.parts[-1]))
167
+
168
+ if field_type_name(field, 0) == "ARRAY" and field_type_name(field, 1) == "STRING":
169
+ # llama.cpp imatrix files usually store a single dataset string.
170
+ return decode_bytes(part_to_list(field.parts[-1]))
171
+
172
+ value = part_to_list(field.parts[-1])
173
+ if isinstance(value, list) and len(value) == 1:
174
+ return value[0]
175
+ return value
176
+
177
+
178
+ def parse_block_module(base: str) -> tuple[int | None, str]:
179
+ for pattern in BLOCK_PATTERNS:
180
+ match = pattern.match(base)
181
+ if match:
182
+ return int(match.group(1)), match.group(2)
183
+ if base.endswith(".weight"):
184
+ return None, base[: -len(".weight")]
185
+ return None, base
186
+
187
+
188
+ def classify_module(module: str, include_router_input: bool = False) -> tuple[str, str, str] | None:
189
+ """Return (kind, projection, family) for FFN modules that this report handles."""
190
+ if module == "ffn_gate_inp" or module.endswith(".ffn_gate_inp"):
191
+ if not include_router_input:
192
+ return None
193
+ return "router", "gate input", "router gate input"
194
+
195
+ if "ffn_down" in module:
196
+ projection = "down"
197
+ elif "ffn_gate" in module or "ffn_up" in module:
198
+ projection = "gate/up"
199
+ else:
200
+ return None
201
+
202
+ if "_exps" in module or ".experts." in module or "_experts" in module:
203
+ kind = "routed expert"
204
+ elif "_shexp" in module or "shared_expert" in module or "shared_experts" in module:
205
+ kind = "shared expert"
206
+ else:
207
+ kind = "dense"
208
+
209
+ return kind, projection, f"{kind} {projection}"
210
+
211
+
212
+ def collect_lengths(reader: GGUFReader) -> dict[tuple[int | None, str], int]:
213
+ lengths: dict[tuple[int | None, str], int] = {}
214
+ for tensor in reader.tensors:
215
+ if not tensor.name.endswith(".in_sum2"):
216
+ continue
217
+ base = tensor.name[: -len(".in_sum2")]
218
+ block, module = parse_block_module(base)
219
+ lengths[(block, module)] = int(np.asarray(tensor.data).size)
220
+ return lengths
221
+
222
+
223
+ def collect_candidates(reader: GGUFReader, include_router_input: bool) -> list[Candidate]:
224
+ tensor_names = {tensor.name for tensor in reader.tensors}
225
+ candidates: list[Candidate] = []
226
+
227
+ for tensor in reader.tensors:
228
+ if not tensor.name.endswith(".in_sum2"):
229
+ continue
230
+
231
+ base = tensor.name[: -len(".in_sum2")]
232
+ count_name = base + ".counts"
233
+ if count_name not in tensor_names:
234
+ continue
235
+
236
+ block, module = parse_block_module(base)
237
+ classified = classify_module(module, include_router_input=include_router_input)
238
+ if classified is None:
239
+ continue
240
+
241
+ kind, projection, family = classified
242
+ candidates.append(
243
+ Candidate(
244
+ base=base,
245
+ tensor_name=tensor.name,
246
+ count_name=count_name,
247
+ block=block,
248
+ module=module,
249
+ kind=kind,
250
+ projection=projection,
251
+ family=family,
252
+ n=int(np.asarray(tensor.data).size),
253
+ )
254
+ )
255
+
256
+ return candidates
257
+
258
+
259
+ def infer_expert_count(candidates: Iterable[Candidate], lengths: dict[tuple[int | None, str], int]) -> tuple[int | None, Counter[int]]:
260
+ candidates = list(candidates)
261
+ gate_ref_modules = {
262
+ "attn_gate",
263
+ "attn_k",
264
+ "attn_q",
265
+ "attn_v",
266
+ "ffn_gate",
267
+ "ffn_up",
268
+ "ffn_gate_shexp",
269
+ "ffn_up_shexp",
270
+ }
271
+ down_ref_modules = {
272
+ "ffn_down",
273
+ "ffn_down_shexp",
274
+ }
275
+
276
+ gate_refs: dict[int | None, set[int]] = defaultdict(set)
277
+ down_refs: dict[int | None, set[int]] = defaultdict(set)
278
+ for (block, module), size in lengths.items():
279
+ if module in gate_ref_modules:
280
+ gate_refs[block].add(size)
281
+ if module in down_ref_modules:
282
+ down_refs[block].add(size)
283
+
284
+ # Prefer dimensions discovered from the FFN candidates themselves. This
285
+ # keeps inference useful for MoE GGUFs that have shared experts, dense
286
+ # leading layers, or non-standard attention tensor availability.
287
+ for candidate in candidates:
288
+ if candidate.block is None or candidate.kind == "routed expert":
289
+ continue
290
+ if candidate.projection == "gate/up":
291
+ gate_refs[candidate.block].add(candidate.n)
292
+ elif candidate.projection == "down":
293
+ down_refs[candidate.block].add(candidate.n)
294
+
295
+ votes: Counter[int] = Counter()
296
+ for candidate in candidates:
297
+ if candidate.kind != "routed expert" or candidate.block is None:
298
+ continue
299
+ refs = gate_refs[candidate.block] if candidate.projection == "gate/up" else down_refs[candidate.block]
300
+ for ref in refs:
301
+ if ref <= 0 or candidate.n % ref != 0:
302
+ continue
303
+ expert_count = candidate.n // ref
304
+ if 1 < expert_count <= 4096:
305
+ votes[expert_count] += 1
306
+
307
+ if not votes:
308
+ return None, votes
309
+ return votes.most_common(1)[0][0], votes
310
+
311
+
312
+ def deduplicate_gate_up(candidates: Iterable[Candidate]) -> list[Candidate]:
313
+ """Keep one tensor for each shared gate/up input stream."""
314
+ by_key: dict[tuple[int | None, str], Candidate] = {}
315
+ for candidate in candidates:
316
+ key = (candidate.block, candidate.family)
317
+ current = by_key.get(key)
318
+ if current is None:
319
+ by_key[key] = candidate
320
+ continue
321
+
322
+ # Prefer gate over up because it is the conventional representative.
323
+ if "ffn_gate" in candidate.module and "ffn_up" in current.module:
324
+ by_key[key] = candidate
325
+
326
+ return list(by_key.values())
327
+
328
+
329
+ def stats(values: np.ndarray) -> tuple[float, float, float, float, float, float, float]:
330
+ mean_mse = float(np.mean(values))
331
+ rms = math.sqrt(mean_mse) if mean_mse >= 0 and math.isfinite(mean_mse) else float("nan")
332
+ return (
333
+ mean_mse,
334
+ rms,
335
+ float(np.percentile(values, 95)),
336
+ float(np.percentile(values, 99)),
337
+ float(np.max(values)),
338
+ float(np.mean(values > 1.0)),
339
+ float(np.mean(values > 4.0)),
340
+ )
341
+
342
+
343
+ def analyze(reader: GGUFReader, args: argparse.Namespace) -> tuple[list[Row], list[ExpertRow], int | None, Counter[int]]:
344
+ tensor_by_name = {tensor.name: tensor for tensor in reader.tensors}
345
+ lengths = collect_lengths(reader)
346
+ candidates = collect_candidates(reader, include_router_input=args.include_router_input)
347
+ inferred_experts, expert_votes = infer_expert_count(candidates, lengths)
348
+ expert_count = args.experts if args.experts is not None else inferred_experts
349
+ candidates = deduplicate_gate_up(candidates)
350
+
351
+ rows: list[Row] = []
352
+ expert_rows: list[ExpertRow] = []
353
+
354
+ for candidate in candidates:
355
+ count = float(np.asarray(tensor_by_name[candidate.count_name].data).reshape(-1)[0])
356
+ raw = np.asarray(tensor_by_name[candidate.tensor_name].data, dtype=np.float64)
357
+ values = raw / count if count else np.full_like(raw, np.nan, dtype=np.float64)
358
+ mean_mse, rms, p95, p99, max_value, gt1, gt4 = stats(values)
359
+ rows.append(
360
+ Row(
361
+ block=candidate.block,
362
+ family=candidate.family,
363
+ kind=candidate.kind,
364
+ projection=candidate.projection,
365
+ tensor=candidate.base,
366
+ count=count,
367
+ n=candidate.n,
368
+ mean_mse=mean_mse,
369
+ rms=rms,
370
+ p95=p95,
371
+ p99=p99,
372
+ max_value=max_value,
373
+ gt1=gt1,
374
+ gt4=gt4,
375
+ )
376
+ )
377
+
378
+ if (
379
+ not args.no_experts
380
+ and expert_count
381
+ and candidate.kind == "routed expert"
382
+ and candidate.block is not None
383
+ and candidate.n % expert_count == 0
384
+ ):
385
+ per_expert = values.reshape(expert_count, candidate.n // expert_count)
386
+ for expert_id, expert_values in enumerate(per_expert):
387
+ exp_mean, exp_rms, exp_p95, exp_p99, exp_max, _, _ = stats(expert_values)
388
+ expert_rows.append(
389
+ ExpertRow(
390
+ block=candidate.block,
391
+ expert=expert_id,
392
+ family=candidate.family,
393
+ tensor=candidate.base,
394
+ count=count,
395
+ n=int(expert_values.size),
396
+ mean_mse=exp_mean,
397
+ rms=exp_rms,
398
+ p95=exp_p95,
399
+ p99=exp_p99,
400
+ max_value=exp_max,
401
+ )
402
+ )
403
+
404
+ return rows, expert_rows, expert_count, expert_votes
405
+
406
+
407
+ def fmt(value: object) -> str:
408
+ if isinstance(value, float):
409
+ if math.isnan(value):
410
+ return ""
411
+ return f"{value:.4g}"
412
+ if isinstance(value, int):
413
+ return str(value)
414
+ return str(value)
415
+
416
+
417
+ def pct(value: float) -> str:
418
+ return f"{100.0 * value:.1f}%"
419
+
420
+
421
+ def block_label(block: int | None) -> str:
422
+ return "?" if block is None else str(block)
423
+
424
+
425
+ def print_table(headers: list[str], rows: Iterable[list[object]]) -> None:
426
+ print("| " + " | ".join(headers) + " |")
427
+ print("| " + " | ".join("---" for _ in headers) + " |")
428
+ for row in rows:
429
+ print("| " + " | ".join(fmt(value) for value in row) + " |")
430
+
431
+
432
+ def metadata(reader: GGUFReader) -> dict[str, object]:
433
+ wanted = ("general.type", "imatrix.datasets", "imatrix.chunk_count", "imatrix.chunk_size")
434
+ return {key: decode_field(reader.fields[key]) for key in wanted if key in reader.fields}
435
+
436
+
437
+ def aggregate_experts(expert_rows: Iterable[ExpertRow]) -> list[dict[str, object]]:
438
+ acc: dict[tuple[int, int], dict[str, object]] = {}
439
+ for row in expert_rows:
440
+ key = (row.block, row.expert)
441
+ item = acc.setdefault(
442
+ key,
443
+ {
444
+ "block": row.block,
445
+ "expert": row.expert,
446
+ "weighted_mse_sum": 0.0,
447
+ "n": 0,
448
+ "max_stream_rms": 0.0,
449
+ "streams": [],
450
+ },
451
+ )
452
+ item["weighted_mse_sum"] = float(item["weighted_mse_sum"]) + row.mean_mse * row.n
453
+ item["n"] = int(item["n"]) + row.n
454
+ item["max_stream_rms"] = max(float(item["max_stream_rms"]), row.rms)
455
+ item["streams"].append(row.family)
456
+
457
+ result: list[dict[str, object]] = []
458
+ for item in acc.values():
459
+ mean_mse = float(item["weighted_mse_sum"]) / int(item["n"])
460
+ result.append(
461
+ {
462
+ "block": item["block"],
463
+ "expert": item["expert"],
464
+ "rms": math.sqrt(mean_mse),
465
+ "mean_mse": mean_mse,
466
+ "max_stream_rms": item["max_stream_rms"],
467
+ "streams": ", ".join(sorted(set(item["streams"]))),
468
+ }
469
+ )
470
+ return result
471
+
472
+
473
+ def render(path: Path, reader: GGUFReader, rows: list[Row], expert_rows: list[ExpertRow], expert_count: int | None, expert_votes: Counter[int], args: argparse.Namespace) -> None:
474
+ print(f"# {path.name}")
475
+ print()
476
+ print("Score: `RMS = sqrt(mean(in_sum2 / counts))`.")
477
+ print()
478
+
479
+ meta = metadata(reader)
480
+ if meta:
481
+ print("Metadata:")
482
+ print()
483
+ print_table(["Key", "Value"], ([key, value] for key, value in meta.items()))
484
+ print()
485
+
486
+ blocks = sorted(row.block for row in rows if row.block is not None)
487
+ if blocks:
488
+ print(f"Blocks covered: `{blocks[0]}-{blocks[-1]}`")
489
+ print()
490
+
491
+ if expert_count:
492
+ source = "command line" if args.experts is not None else "inferred from tensor lengths"
493
+ print(f"Routed expert count: `{expert_count}` ({source}).")
494
+ if expert_votes and args.experts is None:
495
+ vote_text = ", ".join(f"{count}: {votes}" for count, votes in expert_votes.most_common(5))
496
+ print(f"Expert-count inference votes: `{vote_text}`.")
497
+ print()
498
+ elif not args.no_experts:
499
+ print("Routed expert count could not be inferred. Use `--experts N` to enable per-expert rankings.")
500
+ print()
501
+
502
+ rows_sorted = sorted(rows, key=lambda row: row.rms, reverse=True)
503
+ if rows_sorted:
504
+ print(f"Top {min(args.top, len(rows_sorted))} `down` / `gate/up` tensor streams:")
505
+ print()
506
+ print_table(
507
+ ["Block", "Family", "Tensor", "RMS", "Mean MSE", "P95", "Max", "Count"],
508
+ (
509
+ [
510
+ block_label(row.block),
511
+ row.family,
512
+ f"`{row.tensor}`",
513
+ row.rms,
514
+ row.mean_mse,
515
+ row.p95,
516
+ row.max_value,
517
+ int(row.count),
518
+ ]
519
+ for row in rows_sorted[: args.top]
520
+ ),
521
+ )
522
+ print()
523
+
524
+ if expert_rows:
525
+ print(
526
+ "Per-expert rankings assume llama.cpp-style routed expert tensors are flattened "
527
+ "with each expert stored contiguously. Counts are tensor-level counts, so these "
528
+ "rank aggregate activation contribution per expert stream."
529
+ )
530
+ print()
531
+
532
+ aggregated = sorted(aggregate_experts(expert_rows), key=lambda row: float(row["rms"]), reverse=True)
533
+ print(f"Top {min(args.top_experts, len(aggregated))} routed experts, combined across routed down and gate/up:")
534
+ print()
535
+ print_table(
536
+ ["Block", "Expert", "Combined RMS", "Mean MSE", "Max stream RMS", "Streams"],
537
+ (
538
+ [
539
+ row["block"],
540
+ row["expert"],
541
+ row["rms"],
542
+ row["mean_mse"],
543
+ row["max_stream_rms"],
544
+ row["streams"],
545
+ ]
546
+ for row in aggregated[: args.top_experts]
547
+ ),
548
+ )
549
+ print()
550
+
551
+ expert_streams = sorted(expert_rows, key=lambda row: row.rms, reverse=True)
552
+ print(f"Top {min(args.top_expert_streams, len(expert_streams))} routed expert streams:")
553
+ print()
554
+ print_table(
555
+ ["Block", "Expert", "Family", "Tensor", "RMS", "Mean MSE", "P95", "Max", "Count"],
556
+ (
557
+ [
558
+ row.block,
559
+ row.expert,
560
+ row.family,
561
+ f"`{row.tensor}`",
562
+ row.rms,
563
+ row.mean_mse,
564
+ row.p95,
565
+ row.max_value,
566
+ int(row.count),
567
+ ]
568
+ for row in expert_streams[: args.top_expert_streams]
569
+ ),
570
+ )
571
+ print()
572
+
573
+ families = [
574
+ "dense down",
575
+ "dense gate/up",
576
+ "routed expert down",
577
+ "routed expert gate/up",
578
+ "shared expert down",
579
+ "shared expert gate/up",
580
+ "router gate input",
581
+ ]
582
+ print(f"Top {args.family_top} by family:")
583
+ print()
584
+ family_rows: list[list[object]] = []
585
+ for family in families:
586
+ subset = [row for row in rows if row.family == family]
587
+ for row in sorted(subset, key=lambda item: item.rms, reverse=True)[: args.family_top]:
588
+ family_rows.append(
589
+ [
590
+ family,
591
+ block_label(row.block),
592
+ f"`{row.tensor}`",
593
+ row.rms,
594
+ row.mean_mse,
595
+ row.p95,
596
+ row.p99,
597
+ row.max_value,
598
+ pct(row.gt1),
599
+ pct(row.gt4),
600
+ int(row.count),
601
+ ]
602
+ )
603
+ print_table(
604
+ ["Family", "Block", "Tensor", "RMS", "Mean MSE", "P95", "P99", "Max", ">1", ">4", "Count"],
605
+ family_rows,
606
+ )
607
+ print()
608
+
609
+ moe_blocks = sorted(
610
+ {
611
+ row.block
612
+ for row in rows
613
+ if row.block is not None and row.kind in {"routed expert", "shared expert"}
614
+ }
615
+ )
616
+ if args.last_blocks > 0 and moe_blocks:
617
+ selected_blocks = moe_blocks[-args.last_blocks :]
618
+ by_block_family = {(row.block, row.family): row for row in rows}
619
+ print(f"Last {len(selected_blocks)} MoE block RMS:")
620
+ print()
621
+ compact_rows: list[list[object]] = []
622
+ for block in selected_blocks:
623
+ compact_rows.append(
624
+ [
625
+ block,
626
+ by_block_family.get((block, "shared expert down"), "").rms
627
+ if (block, "shared expert down") in by_block_family
628
+ else "",
629
+ by_block_family.get((block, "shared expert gate/up"), "").rms
630
+ if (block, "shared expert gate/up") in by_block_family
631
+ else "",
632
+ by_block_family.get((block, "routed expert down"), "").rms
633
+ if (block, "routed expert down") in by_block_family
634
+ else "",
635
+ by_block_family.get((block, "routed expert gate/up"), "").rms
636
+ if (block, "routed expert gate/up") in by_block_family
637
+ else "",
638
+ ]
639
+ )
640
+ print_table(["Block", "Shared down", "Shared gate/up", "Routed down", "Routed gate/up"], compact_rows)
641
+ print()
642
+
643
+
644
+ def main() -> int:
645
+ args = parse_args()
646
+ if not args.imatrix.exists():
647
+ print(f"error: file not found: {args.imatrix}", file=sys.stderr)
648
+ return 2
649
+
650
+ reader = GGUFReader(str(args.imatrix))
651
+ rows, expert_rows, expert_count, expert_votes = analyze(reader, args)
652
+ if not rows:
653
+ print("error: no FFN down/gate/up imatrix tensors were found", file=sys.stderr)
654
+ return 1
655
+
656
+ render(args.imatrix, reader, rows, expert_rows, expert_count, expert_votes, args)
657
+ return 0
658
+
659
+
660
+ if __name__ == "__main__":
661
+ raise SystemExit(main())
scripts/split.sh ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Usage: split.sh <llama_cpp_dir> <input_gguf> <output_dir> [output_prefix] [--remove-input]
8
+
9
+ Environment overrides:
10
+ SPLIT_MAX_SIZE=50G Maximum size for each split file.
11
+
12
+ Examples:
13
+ ./scripts/split.sh ~/code/llama.cpp model.gguf ./Q5_K_S
14
+ ./scripts/split.sh ~/code/llama.cpp model.gguf ./Q5_K_S model-Q5_K_S --remove-input
15
+ EOF
16
+ }
17
+
18
+ if [ $# -lt 3 ]; then
19
+ usage
20
+ exit 1
21
+ fi
22
+
23
+ LLAMA_CPP_DIR="$1"
24
+ INPUT_GGUF="$2"
25
+ OUTPUT_DIR="$3"
26
+ OUTPUT_PREFIX_NAME=
27
+ REMOVE_INPUT=false
28
+ shift 3
29
+
30
+ while [ $# -gt 0 ]; do
31
+ case "$1" in
32
+ --remove-input)
33
+ REMOVE_INPUT=true
34
+ ;;
35
+ --*)
36
+ usage
37
+ exit 1
38
+ ;;
39
+ *)
40
+ if [ -n "$OUTPUT_PREFIX_NAME" ]; then
41
+ usage
42
+ exit 1
43
+ fi
44
+ OUTPUT_PREFIX_NAME="$1"
45
+ ;;
46
+ esac
47
+ shift
48
+ done
49
+
50
+ if [ ! -d "$LLAMA_CPP_DIR" ]; then
51
+ echo "Error: llama.cpp directory not found: $LLAMA_CPP_DIR" >&2
52
+ exit 1
53
+ fi
54
+
55
+ if [ ! -e "$INPUT_GGUF" ]; then
56
+ echo "Error: input GGUF not found: $INPUT_GGUF" >&2
57
+ exit 1
58
+ fi
59
+
60
+ SPLIT_BIN="$LLAMA_CPP_DIR/build/bin/llama-gguf-split"
61
+ if [ ! -x "$SPLIT_BIN" ]; then
62
+ echo "Error: llama-gguf-split binary not found: $SPLIT_BIN" >&2
63
+ exit 1
64
+ fi
65
+
66
+ if [ -z "$OUTPUT_PREFIX_NAME" ]; then
67
+ input_basename="$(basename "$INPUT_GGUF")"
68
+ OUTPUT_PREFIX_NAME="${input_basename%.gguf}"
69
+ fi
70
+
71
+ SPLIT_MAX_SIZE="${SPLIT_MAX_SIZE:-50G}"
72
+ mkdir -p "$OUTPUT_DIR"
73
+ OUTPUT_PREFIX="$OUTPUT_DIR/$OUTPUT_PREFIX_NAME"
74
+
75
+ "$SPLIT_BIN" \
76
+ --split-max-size "$SPLIT_MAX_SIZE" \
77
+ --no-tensor-first-split \
78
+ "$INPUT_GGUF" \
79
+ "$OUTPUT_PREFIX"
80
+
81
+ if [ "$REMOVE_INPUT" = true ]; then
82
+ rm -f "$INPUT_GGUF"
83
+ fi
84
+
85
+ echo "Split complete. Output saved to: $OUTPUT_DIR"
scripts/tokenizer_config.patch ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --- tokenizer_config.json
2
+ +++ tokenizer_config.json
3
+ @@ -24,7 +24,7 @@
4
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
5
+ "processor_class": "Qwen3VLProcessor",
6
+ "split_special_tokens": false,
7
+ - "tokenizer_class": "TokenizersBackend",
8
+ + "tokenizer_class": "Qwen2Tokenizer",
9
+ "unk_token": null,
10
+ "video_token": "<|video_pad|>",
11
+ "vision_bos_token": "<|vision_start|>",