funasr-mlx 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- funasr_mlx/__init__.py +3 -0
- funasr_mlx/api.py +92 -0
- funasr_mlx/checkpoint_stream.py +138 -0
- funasr_mlx/checkpoints.py +857 -0
- funasr_mlx/cli.py +275 -0
- funasr_mlx/download.py +147 -0
- funasr_mlx/frontend.py +250 -0
- funasr_mlx/llm_asr.py +306 -0
- funasr_mlx/memory.py +323 -0
- funasr_mlx/models/__init__.py +1 -0
- funasr_mlx/models/_layout_state.py +45 -0
- funasr_mlx/models/_strict_load.py +147 -0
- funasr_mlx/models/campplus.py +941 -0
- funasr_mlx/models/ct_punc.py +796 -0
- funasr_mlx/models/emotion2vec.py +947 -0
- funasr_mlx/models/fsmn_vad.py +1177 -0
- funasr_mlx/models/fun_asr_nano.py +211 -0
- funasr_mlx/models/glm_asr.py +143 -0
- funasr_mlx/models/glm_asr_thinker.py +80 -0
- funasr_mlx/models/llm/__init__.py +14 -0
- funasr_mlx/models/llm/awq.py +377 -0
- funasr_mlx/models/llm/calibrate.py +179 -0
- funasr_mlx/models/llm/gptq.py +701 -0
- funasr_mlx/models/llm/kv_cache.py +57 -0
- funasr_mlx/models/llm/llama.py +136 -0
- funasr_mlx/models/llm/quant.py +297 -0
- funasr_mlx/models/llm/qwen3.py +168 -0
- funasr_mlx/models/llm/rope.py +35 -0
- funasr_mlx/models/llm/splice.py +38 -0
- funasr_mlx/models/moss_transcribe.py +160 -0
- funasr_mlx/models/paraformer.py +898 -0
- funasr_mlx/models/paraformer_streaming.py +406 -0
- funasr_mlx/models/qwen3_asr.py +186 -0
- funasr_mlx/models/qwen3_asr_thinker.py +85 -0
- funasr_mlx/models/sanm.py +789 -0
- funasr_mlx/models/sensevoice.py +451 -0
- funasr_mlx/models/whisper.py +980 -0
- funasr_mlx/pipeline.py +196 -0
- funasr_mlx/pseudo_streaming.py +103 -0
- funasr_mlx/runtime.py +82 -0
- funasr_mlx/server.py +97 -0
- funasr_mlx-0.1.0.dist-info/METADATA +255 -0
- funasr_mlx-0.1.0.dist-info/RECORD +46 -0
- funasr_mlx-0.1.0.dist-info/WHEEL +4 -0
- funasr_mlx-0.1.0.dist-info/entry_points.txt +2 -0
- funasr_mlx-0.1.0.dist-info/licenses/LICENSE +26 -0
funasr_mlx/__init__.py
ADDED
funasr_mlx/api.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Port notes: docs/port-notes/api.py.md"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .memory import MemoryBudgetExceeded
|
|
12
|
+
|
|
13
|
+
def download_model(repo, *, variant=None, dest=".", owner=None):
|
|
14
|
+
# see docs/port-notes/functions.md :: download_model
|
|
15
|
+
from .download import download
|
|
16
|
+
|
|
17
|
+
kwargs = {"variant": variant, "dest": dest}
|
|
18
|
+
if owner is not None:
|
|
19
|
+
kwargs["owner"] = owner
|
|
20
|
+
return download(repo, **kwargs)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pseudo_stream(model, *, family: str | None = None, chunk_seconds: float = 1.0):
|
|
24
|
+
# see docs/port-notes/functions.md :: pseudo_stream
|
|
25
|
+
from .pseudo_streaming import PseudoStream
|
|
26
|
+
|
|
27
|
+
transcriber = model if hasattr(model, "transcribe_samples") else TranscribeWorker(
|
|
28
|
+
model, family=family
|
|
29
|
+
)._impl
|
|
30
|
+
return PseudoStream(
|
|
31
|
+
transcribe_fn=transcriber.transcribe_samples,
|
|
32
|
+
chunk_seconds=chunk_seconds,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = ["transcribe", "TranscribeWorker", "download_model", "pseudo_stream"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TranscribeWorker:
|
|
40
|
+
|
|
41
|
+
def __init__(self, checkpoint: str | Path, *, family: str | None = None, max_tokens: int = 96):
|
|
42
|
+
from .llm_asr import LlmAsrTranscriber, resolve_family
|
|
43
|
+
|
|
44
|
+
checkpoint = Path(checkpoint)
|
|
45
|
+
self._family = resolve_family(checkpoint, family)
|
|
46
|
+
self._impl = LlmAsrTranscriber(checkpoint, family=self._family, max_tokens=max_tokens)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def family(self) -> str:
|
|
50
|
+
return self._family
|
|
51
|
+
|
|
52
|
+
def transcribe(self, audio: str | Path) -> dict[str, Any]:
|
|
53
|
+
return self._impl.transcribe_file(audio).to_dict()
|
|
54
|
+
|
|
55
|
+
def pseudo_stream(self, chunk_seconds: float = 1.0):
|
|
56
|
+
from .pseudo_streaming import PseudoStream
|
|
57
|
+
|
|
58
|
+
return PseudoStream(
|
|
59
|
+
transcribe_fn=self._impl.transcribe_samples,
|
|
60
|
+
chunk_seconds=chunk_seconds,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def transcribe(
|
|
65
|
+
audio: str | Path,
|
|
66
|
+
*,
|
|
67
|
+
model: str | Path,
|
|
68
|
+
family: str | None = None,
|
|
69
|
+
max_tokens: int = 96,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
try:
|
|
72
|
+
worker = TranscribeWorker(model, family=family, max_tokens=max_tokens)
|
|
73
|
+
return worker.transcribe(audio)
|
|
74
|
+
except MemoryBudgetExceeded:
|
|
75
|
+
pass # fall through to the guarded CLI subprocess
|
|
76
|
+
|
|
77
|
+
cmd = [sys.executable, "-m", "funasr_mlx.cli", "transcribe", str(model),
|
|
78
|
+
"--audio", str(audio), "--max-tokens", str(max_tokens)]
|
|
79
|
+
if family:
|
|
80
|
+
cmd += ["--family", family]
|
|
81
|
+
env = dict(os.environ)
|
|
82
|
+
env.pop("FUNASR_MLX_MEMORY_POLICY", None) # let the CLI parent set its own
|
|
83
|
+
env["PYTHONPATH"] = str(Path(__file__).resolve().parent.parent)
|
|
84
|
+
proc = subprocess.Popen(
|
|
85
|
+
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=env
|
|
86
|
+
)
|
|
87
|
+
out, err = proc.communicate(timeout=900)
|
|
88
|
+
for line in out.splitlines():
|
|
89
|
+
line = line.strip()
|
|
90
|
+
if line.startswith("{") and '"text"' in line:
|
|
91
|
+
return json.loads(line)
|
|
92
|
+
raise RuntimeError(f"guarded transcribe failed (rc={proc.returncode}): {err[-400:]}")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Port notes: docs/port-notes/checkpoint_stream.py.md"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import struct
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
_CODES = {
|
|
12
|
+
"float32": "F32",
|
|
13
|
+
"float16": "F16",
|
|
14
|
+
"float64": "F64",
|
|
15
|
+
"int64": "I64",
|
|
16
|
+
"int32": "I32",
|
|
17
|
+
"int16": "I16",
|
|
18
|
+
"int8": "I8",
|
|
19
|
+
"uint8": "U8",
|
|
20
|
+
"uint32": "U32",
|
|
21
|
+
"uint64": "U64",
|
|
22
|
+
"bool": "BOOL",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# see docs/port-notes/comments.md :: checkpoint_stream.py:25
|
|
26
|
+
_CODE_BYTES = {
|
|
27
|
+
"BF16": 2,
|
|
28
|
+
"F16": 2,
|
|
29
|
+
"F32": 4,
|
|
30
|
+
"F64": 8,
|
|
31
|
+
"I8": 1,
|
|
32
|
+
"U8": 1,
|
|
33
|
+
"I16": 2,
|
|
34
|
+
"I32": 4,
|
|
35
|
+
"I64": 8,
|
|
36
|
+
"U32": 4,
|
|
37
|
+
"U64": 8,
|
|
38
|
+
"BOOL": 1,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def write_shard(
|
|
43
|
+
path: Path,
|
|
44
|
+
tensors: dict,
|
|
45
|
+
*,
|
|
46
|
+
metadata: dict,
|
|
47
|
+
code: str | None = None,
|
|
48
|
+
codes: dict | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Write a whole shard whose tensors are all carried in one dtype.
|
|
51
|
+
|
|
52
|
+
Full notes: docs/port-notes/functions.md :: write_shard
|
|
53
|
+
"""
|
|
54
|
+
if (code is None) == (codes is None):
|
|
55
|
+
raise ValueError("Pass exactly one of code or codes")
|
|
56
|
+
if code is not None:
|
|
57
|
+
if code not in _CODE_BYTES:
|
|
58
|
+
raise ValueError(f"Unsupported shard type code {code}")
|
|
59
|
+
codes = {name: code for name in tensors}
|
|
60
|
+
header: dict = {"__metadata__": dict(metadata)}
|
|
61
|
+
offset = 0
|
|
62
|
+
for name, array in tensors.items():
|
|
63
|
+
tensor_code = codes.get(name)
|
|
64
|
+
if tensor_code not in _CODE_BYTES:
|
|
65
|
+
raise ValueError(f"Unsupported shard type code {tensor_code} for {name}")
|
|
66
|
+
width = _CODE_BYTES[tensor_code]
|
|
67
|
+
if array.dtype.itemsize != width:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Shard tensor {name} has element width {array.dtype.itemsize}, "
|
|
70
|
+
f"but code {tensor_code} needs {width}"
|
|
71
|
+
)
|
|
72
|
+
size = int(array.size) * width
|
|
73
|
+
header[name] = {
|
|
74
|
+
"dtype": tensor_code,
|
|
75
|
+
"shape": list(array.shape),
|
|
76
|
+
"data_offsets": [offset, offset + size],
|
|
77
|
+
}
|
|
78
|
+
offset += size
|
|
79
|
+
encoded = json.dumps(header, separators=(",", ":")).encode("utf-8")
|
|
80
|
+
encoded += b" " * ((-len(encoded)) % 8)
|
|
81
|
+
temporary = path.with_suffix(path.suffix + ".partial")
|
|
82
|
+
try:
|
|
83
|
+
with temporary.open("wb") as output:
|
|
84
|
+
output.write(struct.pack("<Q", len(encoded)))
|
|
85
|
+
output.write(encoded)
|
|
86
|
+
for array in tensors.values():
|
|
87
|
+
output.write(np.ascontiguousarray(array).tobytes())
|
|
88
|
+
temporary.replace(path)
|
|
89
|
+
finally:
|
|
90
|
+
temporary.unlink(missing_ok=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def write_tensor(
|
|
94
|
+
path: Path,
|
|
95
|
+
name: str,
|
|
96
|
+
array: np.ndarray,
|
|
97
|
+
*,
|
|
98
|
+
dtype,
|
|
99
|
+
metadata,
|
|
100
|
+
chunk_bytes: int = 16_000_000,
|
|
101
|
+
code: str | None = None,
|
|
102
|
+
):
|
|
103
|
+
# see docs/port-notes/functions.md :: write_tensor
|
|
104
|
+
output_dtype = np.dtype(dtype)
|
|
105
|
+
if not array.flags.c_contiguous:
|
|
106
|
+
raise ValueError("Large streaming tensors must be C-contiguous")
|
|
107
|
+
if code is not None:
|
|
108
|
+
if _CODE_BYTES.get(code) != array.dtype.itemsize:
|
|
109
|
+
raise ValueError("Streaming type code disagrees with the array element width")
|
|
110
|
+
output_code = code
|
|
111
|
+
elif not output_dtype.isnative or output_dtype.name not in _CODES:
|
|
112
|
+
raise ValueError("Unsupported streaming tensor dtype")
|
|
113
|
+
else:
|
|
114
|
+
output_code = _CODES[output_dtype.name]
|
|
115
|
+
count = int(array.size)
|
|
116
|
+
header = {
|
|
117
|
+
"__metadata__": dict(metadata),
|
|
118
|
+
name: {
|
|
119
|
+
"dtype": output_code,
|
|
120
|
+
"shape": list(array.shape),
|
|
121
|
+
"data_offsets": [0, count * output_dtype.itemsize],
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
encoded = json.dumps(header, separators=(",", ":")).encode("utf-8")
|
|
125
|
+
encoded += b" " * ((-len(encoded)) % 8)
|
|
126
|
+
temporary = path.with_suffix(path.suffix + ".partial")
|
|
127
|
+
flat = array.reshape(-1)
|
|
128
|
+
stride = max(1, chunk_bytes // max(array.dtype.itemsize, output_dtype.itemsize))
|
|
129
|
+
try:
|
|
130
|
+
with temporary.open("wb") as output:
|
|
131
|
+
output.write(struct.pack("<Q", len(encoded)))
|
|
132
|
+
output.write(encoded)
|
|
133
|
+
for start in range(0, count, stride):
|
|
134
|
+
block = np.asarray(flat[start : start + stride], dtype=output_dtype, order="C")
|
|
135
|
+
output.write(memoryview(block).cast("B"))
|
|
136
|
+
temporary.replace(path)
|
|
137
|
+
finally:
|
|
138
|
+
temporary.unlink(missing_ok=True)
|