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",
]
|