-
-
Notifications
You must be signed in to change notification settings - Fork 6.3k
enable studio for intel GPU #4724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
b759a03
a1c2c4b
6c55664
a293d25
b8b7d47
eebf077
3d579d9
cc04baa
90ffb22
b3ace02
24b2a30
4a0fc3d
3fa23d9
a57adb2
95ac005
c1d9249
3756b5c
efef89a
f32a546
b3844e0
70e573d
7d60f2e
d2e6d15
6d5e73f
95b9e0d
ffb08ba
6611253
79b56d0
c216fd2
3d87200
7db43d7
a4a7140
6df063f
202a1b4
6439a34
117c58f
fd8eb76
7c0ca2a
0663dc5
99ad75d
f537d65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,8 @@ | |||||
|
|
||||||
| import httpx | ||||||
|
|
||||||
| from utils.hardware import clear_gpu_cache | ||||||
|
|
||||||
| logger = get_logger(__name__) | ||||||
|
|
||||||
| # ── Pre-compiled patterns for plan-without-action re-prompt ── | ||||||
|
|
@@ -322,14 +324,19 @@ def _get_gguf_size_bytes(model_path: str) -> int: | |||||
|
|
||||||
| @staticmethod | ||||||
| def _get_gpu_free_memory() -> list[tuple[int, int]]: | ||||||
| """Query free memory per GPU via nvidia-smi. | ||||||
|
|
||||||
| Returns list of (gpu_index, free_mib) sorted by index. | ||||||
| Respects CUDA_VISIBLE_DEVICES if set. | ||||||
| Returns empty list if nvidia-smi is not available. | ||||||
| """Query free memory per visible GPU, backend-aware. | ||||||
|
|
||||||
| Returns list of ``(gpu_index, free_mib)`` sorted by index. The index | ||||||
| space matches whatever the active backend exposes: physical | ||||||
| ``nvidia-smi`` indices on NVIDIA; parent-visible numeric IDs on | ||||||
| AMD/ROCm and Intel XPU (via Studio's hardware telemetry layer). | ||||||
| Returns an empty list if no per-GPU free-memory data is available, | ||||||
| which lets the caller fall through to a non-placement launch path. | ||||||
| """ | ||||||
| import os | ||||||
|
|
||||||
| # Fast path: NVIDIA / nvidia-smi. Cheap, authoritative, and already | ||||||
| # battle-tested across the CUDA fleet. Keep it exactly as-is. | ||||||
| try: | ||||||
| result = subprocess.run( | ||||||
| [ | ||||||
|
|
@@ -341,30 +348,82 @@ def _get_gpu_free_memory() -> list[tuple[int, int]]: | |||||
| text = True, | ||||||
| timeout = 10, | ||||||
| ) | ||||||
| if result.returncode != 0: | ||||||
| if result.returncode == 0: | ||||||
| # Parse which GPUs are allowed by existing CUDA_VISIBLE_DEVICES | ||||||
| allowed = None | ||||||
| cvd = os.environ.get("CUDA_VISIBLE_DEVICES") | ||||||
| if cvd is not None and cvd.strip(): | ||||||
| try: | ||||||
| allowed = set(int(x.strip()) for x in cvd.split(",")) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list comprehension for parsing
Suggested change
References
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropped when llama_cpp.py was reset to main; main's _visible_devices_mask already filters empty tokens. |
||||||
| except ValueError: | ||||||
| pass # Non-numeric (e.g., "GPU-uuid"), ignore filter | ||||||
|
|
||||||
| gpus = [] | ||||||
| for line in result.stdout.strip().splitlines(): | ||||||
| parts = line.split(",") | ||||||
| if len(parts) == 2: | ||||||
| idx = int(parts[0].strip()) | ||||||
| free_mib = int(parts[1].strip()) | ||||||
| if allowed is not None and idx not in allowed: | ||||||
| continue | ||||||
| gpus.append((idx, free_mib)) | ||||||
| if gpus: | ||||||
| return sorted(gpus, key = lambda item: item[0]) | ||||||
| except FileNotFoundError: | ||||||
| pass # nvidia-smi not on PATH — fall through to generic path | ||||||
| except Exception as e: | ||||||
| logger.debug(f"nvidia-smi free-memory query failed: {e}") | ||||||
|
|
||||||
| # Generic path: AMD ROCm, Intel XPU, or any host where nvidia-smi is | ||||||
| # absent / returned empty. Uses Studio's backend-aware telemetry | ||||||
| # layer so XPU hosts pick up free memory via torch.xpu / | ||||||
| # mem_get_info (populated by utils/hardware/hardware.py), and | ||||||
| # ROCm hosts pick up AMD smi data. | ||||||
| try: | ||||||
| from utils.hardware import get_visible_gpu_utilization | ||||||
|
|
||||||
| utilization = get_visible_gpu_utilization() | ||||||
|
|
||||||
| # Refuse to return relative ordinals. When the backend exposes | ||||||
| # the device set via index_kind="relative" (subdevice / wildcard / | ||||||
| # UUID masks), those indices are NOT safe to round-trip back into | ||||||
| # ZE_AFFINITY_MASK or CUDA_VISIBLE_DEVICES because the parent | ||||||
| # process has already hidden the physical ID mapping. Returning | ||||||
| # [] lets the caller skip the placement path entirely and | ||||||
| # inherit the parent's visibility mask unchanged. | ||||||
| if utilization.get("index_kind") not in (None, "physical"): | ||||||
| logger.debug( | ||||||
| "Skipping GPU placement: telemetry reports index_kind=%r " | ||||||
| "(not physical)", | ||||||
| utilization.get("index_kind"), | ||||||
| ) | ||||||
| return [] | ||||||
|
|
||||||
| # Parse which GPUs are allowed by existing CUDA_VISIBLE_DEVICES | ||||||
| allowed = None | ||||||
| cvd = os.environ.get("CUDA_VISIBLE_DEVICES") | ||||||
| if cvd is not None and cvd.strip(): | ||||||
| try: | ||||||
| allowed = set(int(x.strip()) for x in cvd.split(",")) | ||||||
| except ValueError: | ||||||
| pass # Non-numeric (e.g., "GPU-uuid"), ignore filter | ||||||
|
|
||||||
| gpus = [] | ||||||
| for line in result.stdout.strip().splitlines(): | ||||||
| parts = line.split(",") | ||||||
| if len(parts) == 2: | ||||||
| idx = int(parts[0].strip()) | ||||||
| free_mib = int(parts[1].strip()) | ||||||
| if allowed is not None and idx not in allowed: | ||||||
| continue | ||||||
| gpus.append((idx, free_mib)) | ||||||
| return gpus | ||||||
| gpus: list[tuple[int, int]] = [] | ||||||
| for device in utilization.get("devices", []) or []: | ||||||
| index = device.get("index") | ||||||
|
|
||||||
| # Use explicit ``is None`` checks -- ``or`` would treat an | ||||||
| # idle GPU with vram_used_gb == 0.0 as missing telemetry and | ||||||
| # silently drop a perfectly valid free card. | ||||||
| total_gb = device.get("vram_total_gb") | ||||||
| if total_gb is None: | ||||||
| total_gb = device.get("total_gb") | ||||||
|
|
||||||
| used_gb = device.get("vram_used_gb") | ||||||
| if used_gb is None: | ||||||
| used_gb = device.get("used_gb") | ||||||
|
|
||||||
| if index is None or total_gb is None or used_gb is None: | ||||||
| # Missing telemetry for this device -- skip rather than | ||||||
| # invent a free-memory number that drives placement. | ||||||
| continue | ||||||
|
|
||||||
| free_mib = max(int((float(total_gb) - float(used_gb)) * 1024), 0) | ||||||
| gpus.append((int(index), free_mib)) | ||||||
| return sorted(gpus, key = lambda item: item[0]) | ||||||
| except Exception as e: | ||||||
| logger.debug(f"Failed to query GPU free memory via nvidia-smi: {e}") | ||||||
| logger.debug(f"Generic GPU free-memory query failed: {e}") | ||||||
| return [] | ||||||
|
|
||||||
| @staticmethod | ||||||
|
|
@@ -1512,9 +1571,25 @@ def load_model( | |||||
| f"{new_ld}:{existing_ld}" if existing_ld else new_ld | ||||||
| ) | ||||||
|
|
||||||
| # Pin to selected GPU(s) via CUDA_VISIBLE_DEVICES | ||||||
| # Pin to selected GPU(s) via the backend-appropriate visibility | ||||||
| # env var: CUDA_VISIBLE_DEVICES on NVIDIA/ROCm, ZE_AFFINITY_MASK | ||||||
| # on Intel XPU (llama-server's SYCL build reads ZE_AFFINITY_MASK, | ||||||
| # not CUDA_VISIBLE_DEVICES). | ||||||
| if gpu_indices is not None: | ||||||
| env["CUDA_VISIBLE_DEVICES"] = ",".join(str(i) for i in gpu_indices) | ||||||
| from utils.hardware import get_device | ||||||
| from utils.hardware.hardware import DeviceType | ||||||
|
|
||||||
| mask = ",".join(str(i) for i in gpu_indices) | ||||||
| if get_device() == DeviceType.XPU: | ||||||
| env["ZE_AFFINITY_MASK"] = mask | ||||||
| # Deliberately preserve any inherited CUDA_VISIBLE_DEVICES | ||||||
| # (may be "" on hybrid NVIDIA+Intel hosts to force the | ||||||
| # child onto Intel/SYCL). Popping it here would let a | ||||||
| # SYCL+CUDA llama.cpp build re-discover NVIDIA devices, | ||||||
| # contradicting the design note in apply_gpu_ids() in | ||||||
| # utils/hardware/hardware.py. | ||||||
| else: | ||||||
| env["CUDA_VISIBLE_DEVICES"] = mask | ||||||
|
|
||||||
| self._stdout_lines = [] | ||||||
| self._process = subprocess.Popen( | ||||||
|
|
@@ -1625,10 +1700,7 @@ def unload_model(self) -> bool: | |||||
| if LlamaCppBackend._codec_mgr is not None: | ||||||
| LlamaCppBackend._codec_mgr.unload() | ||||||
| LlamaCppBackend._codec_mgr = None | ||||||
| import torch | ||||||
|
|
||||||
| if torch.cuda.is_available(): | ||||||
| torch.cuda.empty_cache() | ||||||
| clear_gpu_cache() | ||||||
| return True | ||||||
|
|
||||||
| def _kill_process(self): | ||||||
|
|
@@ -3261,6 +3333,11 @@ def init_audio_codec(self, audio_type: str) -> None: | |||||
| if LlamaCppBackend._codec_mgr is None: | ||||||
| LlamaCppBackend._codec_mgr = AudioCodecManager() | ||||||
|
|
||||||
| # Preserve the pre-PR CPU fallback on non-CUDA hosts: the SNAC / | ||||||
| # BiCodec / DAC codecs are not yet validated on Intel XPU, so | ||||||
| # only promote to a GPU device when CUDA is actually available. | ||||||
| # A follow-up can extend this once an XPU-specific codec path is | ||||||
| # added. | ||||||
| device = "cuda" if torch.cuda.is_available() else "cpu" | ||||||
| model_repo_path = None | ||||||
|
|
||||||
|
|
@@ -3333,6 +3410,8 @@ def generate_audio_response( | |||||
| else None | ||||||
| ) | ||||||
|
|
||||||
| # Match init_audio_codec: stay on CPU for non-CUDA hosts until the | ||||||
| # codec path is validated on XPU. | ||||||
| import torch | ||||||
|
|
||||||
| device = "cuda" if torch.cuda.is_available() else "cpu" | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1540,6 +1540,10 @@ def _preprocess_snac_dataset(self, dataset, custom_format_mapping = None): | |
|
|
||
| SNAC_MODEL_NAME = "hubertsiuzdak/snac_24khz" | ||
| SNAC_SAMPLE_RATE = 24000 | ||
|
|
||
| # SNAC codec has not been validated on Intel XPU yet; keep the | ||
| # pre-PR CPU fallback for non-CUDA hosts until an XPU-specific | ||
| # path is added. | ||
| device = "cuda" if torch.cuda.is_available() else "cpu" | ||
| max_length = self.max_seq_length or 2048 | ||
| tokenizer = self.tokenizer | ||
|
|
@@ -1716,7 +1720,8 @@ def _preprocess_snac_dataset(self, dataset, custom_format_mapping = None): | |
| import gc | ||
|
|
||
| gc.collect() | ||
| torch.cuda.empty_cache() | ||
|
|
||
| clear_gpu_cache() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. clear_gpu_cache is imported at the top of trainer.py (line 35, first name in the module-level from utils.hardware import block), and all five call sites resolve; CI is green on this head. No NameError is possible here. |
||
| self._cuda_audio_used = True | ||
|
|
||
| if not processed_examples: | ||
|
|
@@ -1744,6 +1749,8 @@ def _preprocess_bicodec_dataset(self, dataset, custom_format_mapping = None): | |
|
|
||
| import subprocess | ||
|
|
||
| # Spark-TTS BiCodec has not been validated on Intel XPU; keep the | ||
| # pre-PR CPU fallback for non-CUDA hosts. | ||
| device = "cuda" if torch.cuda.is_available() else "cpu" | ||
|
|
||
| # The sparktts Python package lives in the SparkAudio/Spark-TTS GitHub repo, | ||
|
|
@@ -1944,7 +1951,8 @@ def extract_wav2vec2_features(wavs: torch.Tensor) -> torch.Tensor: | |
| import gc | ||
|
|
||
| gc.collect() | ||
| torch.cuda.empty_cache() | ||
|
|
||
| clear_gpu_cache() | ||
| self._cuda_audio_used = True | ||
|
|
||
| if not processed_examples: | ||
|
|
@@ -1979,6 +1987,8 @@ def _preprocess_dac_dataset(self, dataset, custom_format_mapping = None): | |
| from datasets import Dataset as HFDataset | ||
| from utils.paths import ensure_dir, tmp_root | ||
|
|
||
| # OuteTTS DAC/Whisper preprocess has not been validated on Intel | ||
| # XPU; keep the pre-PR CPU fallback for non-CUDA hosts. | ||
| device = "cuda" if torch.cuda.is_available() else "cpu" | ||
|
|
||
| # Clone OuteTTS repo (same as audio_codecs._load_dac) | ||
|
|
@@ -2157,7 +2167,8 @@ def _preprocess_dac_dataset(self, dataset, custom_format_mapping = None): | |
| import gc | ||
|
|
||
| gc.collect() | ||
| torch.cuda.empty_cache() | ||
|
|
||
| clear_gpu_cache() | ||
| self._cuda_audio_used = True | ||
|
|
||
| if not processed_examples: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Autocast is only designed for low-precision mixed precision (such as
float16orbfloat16). If the model's dtype isfloat32(or any other non-low-precision dtype), running autocast is redundant and will raise aValueErrorin PyTorch on other backends like CUDA or MPS. We can simplify this check to usecontextlib.nullcontext()wheneverautocast_dtype_supportedisFalse, regardless of the device type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fp32 skip is deliberately limited to cpu/xpu, where autocast raises on fp32. CUDA warns and disables autocast for fp32, which is exactly the behavior main has always shipped (the current code produces a context identical to main's hardcoded autocast('cuda', dtype=model.dtype) for every CUDA model). Extending the skip to CUDA/MPS would change NVIDIA behavior, which is out of scope for this Intel enablement PR.