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.
Files changed (46) hide show
  1. funasr_mlx/__init__.py +3 -0
  2. funasr_mlx/api.py +92 -0
  3. funasr_mlx/checkpoint_stream.py +138 -0
  4. funasr_mlx/checkpoints.py +857 -0
  5. funasr_mlx/cli.py +275 -0
  6. funasr_mlx/download.py +147 -0
  7. funasr_mlx/frontend.py +250 -0
  8. funasr_mlx/llm_asr.py +306 -0
  9. funasr_mlx/memory.py +323 -0
  10. funasr_mlx/models/__init__.py +1 -0
  11. funasr_mlx/models/_layout_state.py +45 -0
  12. funasr_mlx/models/_strict_load.py +147 -0
  13. funasr_mlx/models/campplus.py +941 -0
  14. funasr_mlx/models/ct_punc.py +796 -0
  15. funasr_mlx/models/emotion2vec.py +947 -0
  16. funasr_mlx/models/fsmn_vad.py +1177 -0
  17. funasr_mlx/models/fun_asr_nano.py +211 -0
  18. funasr_mlx/models/glm_asr.py +143 -0
  19. funasr_mlx/models/glm_asr_thinker.py +80 -0
  20. funasr_mlx/models/llm/__init__.py +14 -0
  21. funasr_mlx/models/llm/awq.py +377 -0
  22. funasr_mlx/models/llm/calibrate.py +179 -0
  23. funasr_mlx/models/llm/gptq.py +701 -0
  24. funasr_mlx/models/llm/kv_cache.py +57 -0
  25. funasr_mlx/models/llm/llama.py +136 -0
  26. funasr_mlx/models/llm/quant.py +297 -0
  27. funasr_mlx/models/llm/qwen3.py +168 -0
  28. funasr_mlx/models/llm/rope.py +35 -0
  29. funasr_mlx/models/llm/splice.py +38 -0
  30. funasr_mlx/models/moss_transcribe.py +160 -0
  31. funasr_mlx/models/paraformer.py +898 -0
  32. funasr_mlx/models/paraformer_streaming.py +406 -0
  33. funasr_mlx/models/qwen3_asr.py +186 -0
  34. funasr_mlx/models/qwen3_asr_thinker.py +85 -0
  35. funasr_mlx/models/sanm.py +789 -0
  36. funasr_mlx/models/sensevoice.py +451 -0
  37. funasr_mlx/models/whisper.py +980 -0
  38. funasr_mlx/pipeline.py +196 -0
  39. funasr_mlx/pseudo_streaming.py +103 -0
  40. funasr_mlx/runtime.py +82 -0
  41. funasr_mlx/server.py +97 -0
  42. funasr_mlx-0.1.0.dist-info/METADATA +255 -0
  43. funasr_mlx-0.1.0.dist-info/RECORD +46 -0
  44. funasr_mlx-0.1.0.dist-info/WHEEL +4 -0
  45. funasr_mlx-0.1.0.dist-info/entry_points.txt +2 -0
  46. funasr_mlx-0.1.0.dist-info/licenses/LICENSE +26 -0
funasr_mlx/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Port notes: docs/port-notes/__init__.py.md"""
2
+
3
+ __version__ = "0.1.0"
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)