AIRsLight commited on
Commit
c6b160c
·
verified ·
1 Parent(s): 784c2f2

Add Q5_0 and Q4_0 GGUF quantizations

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ paraformer-q4_0.gguf filter=lfs diff=lfs merge=lfs -text
37
+ paraformer-q5_0.gguf filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 FunASR
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - zh
5
+ - en
6
+ base_model: funasr/paraformer-zh
7
+ pipeline_tag: automatic-speech-recognition
8
+ tags:
9
+ - paraformer
10
+ - funasr
11
+ - gguf
12
+ - quantized
13
+ ---
14
+
15
+ # paraformer-zh GGUF
16
+
17
+ Experimental low-memory GGUF quantizations of
18
+ [`funasr/paraformer-zh`](https://huggingface.co/funasr/paraformer-zh).
19
+ The vocabulary and CMVN tensors are embedded in each file.
20
+
21
+ ## Files
22
+
23
+ | File | Size | Quantization | Intended use |
24
+ | --- | ---: | --- | --- |
25
+ | `paraformer-q5_0.gguf` | 149.7 MiB | Q5_0 matrices, remaining tensors F32 | Recommended low-memory balance |
26
+ | `paraformer-q4_0.gguf` | 124.3 MiB | Q4_0 matrices, remaining tensors F32 | Experimental minimum-memory option |
27
+
28
+ The original exporter only supports F32, F16, and Q8_0. These files were
29
+ exported directly from the official FP32 checkpoint with an extended exporter;
30
+ they were not requantized from Q8.
31
+
32
+ ## Measured behavior
33
+
34
+ Measurements used the persistent CPU Paraformer GGUF runtime on an Intel
35
+ Core i7-14700KF and a 30-second Mandarin/English mixed-language clip.
36
+
37
+ | Quantization | Resident peak working set | Recognition time |
38
+ | --- | ---: | ---: |
39
+ | Q8_0 upstream baseline | 252.8 MiB | 996 ms |
40
+ | Q5_0 | 176.5 MiB | 1,201 ms |
41
+ | Q4_0 | 151.1 MiB | 1,009 ms |
42
+
43
+ Across 59 clips from the same recording, Q5_0 and Q4_0 each matched the Q8_0
44
+ token sequence exactly on 18 clips. Their aggregate token edit distance from
45
+ Q8_0 was 8.8% and 10.1%, respectively. This is output drift relative to Q8,
46
+ not a labeled word-error-rate measurement. Validate the quantization with your
47
+ own language and acoustic conditions before replacing Q8 in quality-sensitive
48
+ deployments.
49
+
50
+ ## Compatibility
51
+
52
+ These files use the custom `paraformer` GGUF architecture implemented by the
53
+ FunASR `runtime/llama.cpp/paraformer` runtime. They are not language models and
54
+ cannot be loaded by general-purpose llama.cpp applications.
55
+
56
+ The compatible persistent Windows runtime and its English BPE spacing patch
57
+ are maintained in the
58
+ [`VRChatVoiceInput`](https://github.com/AIRsLight/VRChatVoiceInput) project.
59
+
60
+ ## Source integrity
61
+
62
+ The export input was the official `model.pt` file:
63
+
64
+ ```text
65
+ size: 880502012 bytes
66
+ sha256: 5bba782a5e9196166233b9ab12ba04cadff9ef9212b4ff6153ed9290ff679025
67
+ ```
68
+
69
+ Exported files:
70
+
71
+ ```text
72
+ paraformer-q5_0.gguf
73
+ size: 156967168 bytes
74
+ sha256: 1f2309eacd761c1f4184177c718321cd6ad3c07e7b17d6f796e5fb15565906ec
75
+
76
+ paraformer-q4_0.gguf
77
+ size: 130313216 bytes
78
+ sha256: 992562722aa2c4e88158245a0fc5be0e1338c580db808a97c6262f6864317584
79
+ ```
80
+
81
+ ## License
82
+
83
+ The base model and the FunASR exporter/runtime are provided under Apache-2.0.
84
+ See `LICENSE` and the base model repository for details.
export_paraformer_low_memory_gguf.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Export Paraformer to GGUF with experimental Q5_0/Q4_0 support."""
3
+
4
+ import argparse
5
+ import glob
6
+ import json
7
+ import os
8
+ import re
9
+
10
+ import gguf
11
+ import numpy as np
12
+ import torch
13
+
14
+
15
+ def parse_mvn(path):
16
+ blocks = [
17
+ np.array([float(x) for x in block.split()], np.float32)
18
+ for block in re.findall(r"\[([^\]]*)\]", open(path).read())
19
+ ]
20
+ vectors = [block for block in blocks if block.size > 1]
21
+ return vectors[0], vectors[1]
22
+
23
+
24
+ def main():
25
+ parser = argparse.ArgumentParser()
26
+ parser.add_argument("--model_pt", required=True)
27
+ parser.add_argument("--mvn", required=True)
28
+ parser.add_argument("--out", required=True)
29
+ parser.add_argument(
30
+ "--wtype",
31
+ default="q5_0",
32
+ choices=["f32", "f16", "q8_0", "q5_0", "q4_0"],
33
+ )
34
+ parser.add_argument("--tokens", default=None)
35
+ args = parser.parse_args()
36
+
37
+ state_dict = torch.load(args.model_pt, map_location="cpu")
38
+ state_dict = state_dict.get("state_dict", state_dict)
39
+ writer = gguf.GGUFWriter(args.out, "paraformer")
40
+ writer.add_uint32("pf.enc.output_size", 512)
41
+ writer.add_uint32("pf.enc.attention_heads", 4)
42
+ writer.add_uint32("pf.enc.num_blocks", 50)
43
+ writer.add_uint32("pf.enc.kernel_size", 11)
44
+ writer.add_uint32("pf.dec.num_blocks", 16)
45
+ writer.add_uint32("pf.dec.att_layer_num", 16)
46
+ writer.add_uint32("pf.dec.decoders3", 1)
47
+ writer.add_uint32("pf.dec.attention_heads", 4)
48
+ writer.add_uint32("pf.dec.kernel_size", 11)
49
+ writer.add_uint32("pf.vocab_size", 8404)
50
+
51
+ token_path = args.tokens or (
52
+ glob.glob(os.path.join(os.path.dirname(args.model_pt), "tokens.json")) + [None]
53
+ )[0]
54
+ if token_path and os.path.exists(token_path):
55
+ with open(token_path, encoding="utf-8") as token_file:
56
+ tokens = json.load(token_file)
57
+ writer.add_array("pf.vocab", tokens)
58
+ print(f"embedded pf.vocab ({len(tokens)} tokens) from {token_path}")
59
+ else:
60
+ print("WARNING: tokens.json not found - GGUF will have no vocabulary")
61
+
62
+ writer.add_float32("pf.predictor.tail_threshold", 0.45)
63
+ writer.add_float32("pf.predictor.threshold", 1.0)
64
+ shift, scale = parse_mvn(args.mvn)
65
+ writer.add_tensor("cmvn.shift", shift)
66
+ writer.add_tensor("cmvn.scale", scale)
67
+
68
+ from gguf import GGMLQuantizationType as QuantType
69
+ from gguf import quants
70
+
71
+ quant_types = {
72
+ "q8_0": QuantType.Q8_0,
73
+ "q5_0": QuantType.Q5_0,
74
+ "q4_0": QuantType.Q4_0,
75
+ }
76
+ quant_type = quant_types.get(args.wtype)
77
+ quant_block_size = (
78
+ quants._type_traits[quant_type].block_size if quant_type is not None else 1
79
+ )
80
+ tensor_count = 0
81
+ quantized_count = 0
82
+
83
+ for name, value in state_dict.items():
84
+ if not name.startswith(("encoder.", "decoder.", "predictor.")):
85
+ continue
86
+ if name == "decoder.embed.0.weight":
87
+ continue
88
+
89
+ array = value.detach().to(torch.float32).contiguous().numpy()
90
+ if name.endswith("fsmn_block.weight") and array.ndim == 3:
91
+ array = np.ascontiguousarray(array[:, 0, :].T)
92
+ elif (
93
+ args.wtype == "f16"
94
+ and array.ndim == 2
95
+ and "norm" not in name
96
+ and "cif_output" not in name
97
+ ):
98
+ array = array.astype(np.float16)
99
+
100
+ can_quantize = (
101
+ quant_type is not None
102
+ and array.ndim == 2
103
+ and "norm" not in name
104
+ and "fsmn_block" not in name
105
+ and "predictor" not in name
106
+ and array.shape[1] % quant_block_size == 0
107
+ )
108
+ if can_quantize:
109
+ writer.add_tensor(
110
+ name,
111
+ quants.quantize(array, quant_type),
112
+ raw_dtype=quant_type,
113
+ )
114
+ quantized_count += 1
115
+ else:
116
+ writer.add_tensor(name, array)
117
+ tensor_count += 1
118
+
119
+ print(
120
+ f"writing {tensor_count} tensors (+cmvn), {quantized_count} quantized "
121
+ f"as {args.wtype}, to {args.out}"
122
+ )
123
+ writer.write_header_to_file()
124
+ writer.write_kv_data_to_file()
125
+ writer.write_tensors_to_file()
126
+ writer.close()
127
+ print(f"done: {args.out} ({os.path.getsize(args.out) / 1e6:.1f} MB)")
128
+
129
+
130
+ if __name__ == "__main__":
131
+ main()
paraformer-q4_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:992562722aa2c4e88158245a0fc5be0e1338c580db808a97c6262f6864317584
3
+ size 130313216
paraformer-q5_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1f2309eacd761c1f4184177c718321cd6ad3c07e7b17d6f796e5fb15565906ec
3
+ size 156967168