File size: 5,681 Bytes
1ba7218
 
 
 
 
 
0ac9178
 
1ba7218
 
a6beab2
 
 
0ac9178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a6beab2
 
 
 
 
 
 
 
 
 
 
 
 
0ac9178
 
 
 
 
 
a6beab2
0ac9178
 
 
 
 
 
 
 
a6beab2
0ac9178
a6beab2
 
0ac9178
 
 
 
 
 
 
 
 
 
 
a6beab2
0ac9178
 
 
 
 
 
 
 
 
 
 
 
 
 
a6beab2
0ac9178
 
 
 
 
 
 
 
 
 
1ba7218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""Shared evaluation runtime: device selection, ZeroGPU bypass, CPU thread hints."""

from __future__ import annotations

import importlib.util
import os
import sys
import types

_cpu_thread_settings_applied = False
_torchcodec_patch_applied = False


def _force_transformers_torchcodec_unavailable() -> None:
    """Tell transformers to use librosa/soundfile for audio, not torchcodec."""
    try:
        import transformers.utils.import_utils as import_utils

        import_utils._torchcodec_available = False

        def _is_torchcodec_available() -> bool:
            return False

        import_utils.is_torchcodec_available = _is_torchcodec_available
    except Exception:
        pass


def disable_broken_torchcodec() -> None:
    """
    Transformers prefers torchcodec for ``load_audio`` when the package is installed,
    but Hub Job images often lack FFmpeg shared libraries (libavutil.so.*).

    If torchcodec cannot load, mark it unavailable so transformers falls back to
    librosa / soundfile.
    """
    global _torchcodec_patch_applied
    if _torchcodec_patch_applied:
        return
    _torchcodec_patch_applied = True

    force_off = os.environ.get("FFASR_DISABLE_TORCHCODEC", "").strip().lower() in (
        "1",
        "true",
        "yes",
        "on",
    )

    if force_off or importlib.util.find_spec("torchcodec") is None:
        if force_off and importlib.util.find_spec("torchcodec") is not None:
            print(
                "[ffasr] FFASR_DISABLE_TORCHCODEC=1: using librosa/soundfile for audio "
                "(torchcodec disabled on Hub Jobs).",
                flush=True,
            )
        _force_transformers_torchcodec_unavailable()
        return

    try:
        from torchcodec.decoders import AudioDecoder  # noqa: F401
    except (OSError, RuntimeError, ImportError, AttributeError):
        print(
            "[ffasr] torchcodec native libraries unavailable; "
            "transformers will use librosa/soundfile for audio.",
            flush=True,
        )
        _force_transformers_torchcodec_unavailable()
        stub = types.ModuleType("torchcodec")
        stub.__dict__["decoders"] = types.ModuleType("torchcodec.decoders")
        sys.modules["torchcodec"] = stub
        sys.modules["torchcodec.decoders"] = stub.__dict__["decoders"]
    except Exception:
        _force_transformers_torchcodec_unavailable()


def patch_transformers_load_audio_for_paths() -> None:
    """
    Route ``transformers.audio_utils.load_audio`` for file paths through soundfile
    (no torchcodec / FFmpeg). Safe to call before loading user custom scripts.
    """
    try:
        import numpy as np
        import transformers.audio_utils as audio_utils
        from backends._audio_utils import load_wav_mono

        _orig = audio_utils.load_audio

        def _load_audio(audio, sampling_rate=16000, timeout=None):
            if isinstance(audio, str):
                return load_wav_mono(audio, sampling_rate=int(sampling_rate))
            if isinstance(audio, np.ndarray):
                return audio
            return _orig(audio, sampling_rate=sampling_rate, timeout=timeout)

        audio_utils.load_audio = _load_audio
    except Exception:
        pass


def zerogpu_disabled() -> bool:
    """When True, never wrap evaluation in ``spaces.GPU`` even if ``spaces`` is installed."""
    return os.environ.get("FFASR_DISABLE_ZEROGPU", "").strip().lower() in (
        "1",
        "true",
        "yes",
        "on",
    )


def spaces_available() -> bool:
    return importlib.util.find_spec("spaces") is not None


def use_spaces_gpu_decorator() -> bool:
    """Use ZeroGPU decorator only when ``spaces`` is present and not explicitly disabled."""
    return spaces_available() and not zerogpu_disabled()


def resolve_eval_devices() -> tuple[str, int]:
    """
    Return ``(device_str, device_int)`` for backends / HF pipeline.

    ``FFASR_DEVICE`` (default ``auto``): ``cpu`` | ``cuda`` | ``auto``.
    ``auto`` uses CUDA when available, else CPU.
    """
    choice = (os.environ.get("FFASR_DEVICE", "auto") or "auto").strip().lower()
    if choice not in ("auto", "cpu", "cuda"):
        choice = "auto"

    import torch

    has_cuda = torch.cuda.is_available()
    if choice == "cpu":
        return "cpu", -1
    if choice == "cuda":
        if not has_cuda:
            raise RuntimeError("FFASR_DEVICE=cuda but torch.cuda.is_available() is False.")
        return "cuda", 0
    # auto
    if has_cuda:
        return "cuda", 0
    return "cpu", -1


def apply_cpu_thread_settings_once() -> None:
    """
    Apply optional torch thread limits from env (once per process).

    - ``FFASR_TORCH_NUM_THREADS`` — passed to ``torch.set_num_threads`` if a positive int.
    - ``FFASR_TORCH_NUM_INTEROP_THREADS`` — ``torch.set_num_interop_threads`` if set.

    For BLAS/OpenMP, operators typically set ``OMP_NUM_THREADS`` / ``MKL_NUM_THREADS``
    in the Space environment (see docs); those are read by the native libs at load time.
    """
    global _cpu_thread_settings_applied
    if _cpu_thread_settings_applied:
        return
    _cpu_thread_settings_applied = True

    import torch

    raw = os.environ.get("FFASR_TORCH_NUM_THREADS", "").strip()
    if raw.isdigit():
        n = int(raw)
        if n > 0:
            try:
                torch.set_num_threads(n)
            except Exception:
                pass

    raw_i = os.environ.get("FFASR_TORCH_NUM_INTEROP_THREADS", "").strip()
    if raw_i.isdigit():
        ni = int(raw_i)
        if ni > 0:
            try:
                torch.set_num_interop_threads(ni)
            except Exception:
                pass