File size: 7,672 Bytes
708f4a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
"""
XERV CRAYON C-Extensions Package
================================

This package contains the native C/C++/CUDA extensions:

- crayon_cpu: AVX2/AVX-512 accelerated CPU tokenizer (always available)
- crayon_cuda: NVIDIA CUDA GPU tokenizer (optional, requires nvcc)
- crayon_rocm: AMD ROCm GPU tokenizer (optional, requires hipcc)

Import Behavior:
    - crayon_cpu is imported eagerly and will raise ImportError if missing
    - crayon_cuda and crayon_rocm are lazy-loaded to avoid import errors
    - Use check_* functions to safely probe availability

Example:
    >>> from crayon.c_ext import crayon_cpu
    >>> from crayon.c_ext import is_cuda_available, is_rocm_available
    >>> 
    >>> if is_cuda_available():
    ...     from crayon.c_ext import crayon_cuda
"""

import sys
from typing import Optional, Tuple

# ============================================================================
# CPU BACKEND (Required - Lazy Import to avoid circular dependencies)
# ============================================================================

_cpu_module: Optional[object] = None
_cpu_checked: bool = False
_cpu_error: Optional[str] = None


def _load_cpu_backend() -> Optional[object]:
    """Internal function to load the CPU backend."""
    global _cpu_checked, _cpu_module, _cpu_error
    
    if _cpu_checked:
        return _cpu_module
    
    _cpu_checked = True
    try:
        # Use absolute import to avoid circular dependency issues
        import crayon.c_ext.crayon_cpu as _cpu
        # Verify it's functional
        if hasattr(_cpu, 'tokenize') and hasattr(_cpu, 'load_dat'):
            _cpu_module = _cpu
            return _cpu_module
        else:
            _cpu_error = "crayon_cpu module missing required functions (tokenize, load_dat)"
            return None
    except ImportError as e:
        _cpu_error = (
            f"Failed to import crayon_cpu extension. {e}\n"
            "Possible causes:\n"
            "  1. The package was not installed correctly (try: pip install --force-reinstall xerv-crayon)\n"
            "  2. The C++ extension failed to compile (check for compiler errors during install)\n"
            "  3. Python version mismatch (Crayon requires Python 3.10+)"
        )
        return None
    except Exception as e:
        _cpu_error = f"Unexpected error loading crayon_cpu: {e}"
        return None


def get_cpu_backend() -> Optional[object]:
    """Get the CPU backend module, loading it if necessary."""
    return _load_cpu_backend()


def is_cpu_available() -> bool:
    """Check if the CPU backend is available."""
    return _load_cpu_backend() is not None


def get_cpu_error() -> Optional[str]:
    """Get the error message if CPU backend is unavailable."""
    _load_cpu_backend()  # Ensure check has run
    return _cpu_error


# Create a proxy object for backward compatibility
class _CPUProxy:
    """Proxy object that lazily loads crayon_cpu when accessed."""
    
    def __getattr__(self, name):
        cpu_module = _load_cpu_backend()
        if cpu_module is None:
            raise ImportError(f"CPU backend not available: {get_cpu_error()}")
        return getattr(cpu_module, name)
    
    def __dir__(self):
        cpu_module = _load_cpu_backend()
        if cpu_module is None:
            return []
        return dir(cpu_module)


# Create the proxy instance
crayon_cpu = _CPUProxy()


# ============================================================================
# GPU BACKENDS (Optional - Lazy Import)
# ============================================================================

_cuda_module: Optional[object] = None
_rocm_module: Optional[object] = None
_cuda_checked: bool = False
_rocm_checked: bool = False
_cuda_error: Optional[str] = None
_rocm_error: Optional[str] = None


def is_cuda_available() -> bool:
    """
    Check if the CUDA backend is available.
    
    Returns:
        True if crayon_cuda can be imported and CUDA is functional.
    """
    global _cuda_checked, _cuda_module, _cuda_error
    
    if _cuda_checked:
        return _cuda_module is not None
    
    _cuda_checked = True
    try:
        from . import crayon_cuda as _cuda
        # Verify it's functional
        _ = _cuda.get_hardware_info()
        _cuda_module = _cuda
        return True
    except ImportError as e:
        _cuda_error = f"ImportError: {e}"
        return False
    except Exception as e:
        _cuda_error = f"RuntimeError: {e}"
        return False


def is_rocm_available() -> bool:
    """
    Check if the ROCm backend is available.
    
    Returns:
        True if crayon_rocm can be imported and ROCm is functional.
    """
    global _rocm_checked, _rocm_module, _rocm_error
    
    if _rocm_checked:
        return _rocm_module is not None
    
    _rocm_checked = True
    try:
        from . import crayon_rocm as _rocm
        # Verify it's functional
        info = _rocm.get_hardware_info()
        if isinstance(info, str) and "Device Not Found" in info:
            _rocm_error = info
            return False
        _rocm_module = _rocm
        return True
    except ImportError as e:
        _rocm_error = f"ImportError: {e}"
        return False
    except Exception as e:
        _rocm_error = f"RuntimeError: {e}"
        return False


def get_cuda_error() -> Optional[str]:
    """Get the error message if CUDA is unavailable."""
    is_cuda_available()  # Ensure check has run
    return _cuda_error


def get_rocm_error() -> Optional[str]:
    """Get the error message if ROCm is unavailable."""
    is_rocm_available()  # Ensure check has run
    return _rocm_error


def get_available_backends() -> Tuple[str, ...]:
    """
    Get list of available backends.
    
    Returns:
        Tuple of available backend names ("cpu", "cuda", "rocm").
    """
    backends = ["cpu"]
    if is_cuda_available():
        backends.append("cuda")
    if is_rocm_available():
        backends.append("rocm")
    return tuple(backends)


def get_backend_info() -> dict:
    """
    Get detailed information about all backends.
    
    Returns:
        Dictionary with backend status and hardware info.
    """
    info = {
        "cpu": {
            "available": True,
            "hardware": crayon_cpu.get_hardware_info() if hasattr(crayon_cpu, 'get_hardware_info') else "Unknown"
        }
    }
    
    if is_cuda_available():
        try:
            from . import crayon_cuda
            hw = crayon_cuda.get_hardware_info()
            info["cuda"] = {"available": True, "hardware": hw}
        except Exception as e:
            info["cuda"] = {"available": False, "error": str(e)}
    else:
        info["cuda"] = {"available": False, "error": _cuda_error}
    
    if is_rocm_available():
        try:
            from . import crayon_rocm
            hw = crayon_rocm.get_hardware_info()
            info["rocm"] = {"available": True, "hardware": hw}
        except Exception as e:
            info["rocm"] = {"available": False, "error": str(e)}
    else:
        info["rocm"] = {"available": False, "error": _rocm_error}
    
    return info


# ============================================================================
# CONDITIONAL IMPORTS FOR TYPE CHECKING
# ============================================================================

# These will fail at runtime if not available, which is intentional
# Use is_cuda_available() / is_rocm_available() before importing

__all__ = [
    "crayon_cpu",
    "is_cpu_available",
    "get_cpu_backend",
    "get_cpu_error",
    "is_cuda_available",
    "is_rocm_available",
    "get_cuda_error",
    "get_rocm_error",
    "get_available_backends",
    "get_backend_info",
]