| """Threaded Fish Speech queue for standalone mixed NVFP4 checkpoints.""" |
|
|
| from __future__ import annotations |
|
|
| import queue |
| import threading |
| import traceback |
| from pathlib import Path |
|
|
| import torch |
| from loguru import logger |
|
|
| from fish_speech.models.text2semantic.inference import ( |
| GenerateRequest, |
| WrappedGenerateResponse, |
| decode_one_token_ar, |
| generate_long, |
| ) |
|
|
| from experimental.fp8 import MXFP8Linear |
| from .checkpoint import load_mixed_nvfp4_checkpoint |
| from .modules import NVFP4Linear |
|
|
|
|
| def launch_mixed_nvfp4_thread_safe_queue( |
| checkpoint_path: str | Path, |
| device: str | torch.device, |
| precision: torch.dtype, |
| compile: bool = False, |
| *, |
| max_length: int = 3072, |
| verify_checksums: bool = False, |
| ) -> queue.Queue: |
| """Load the frozen mixed checkpoint once and serialize generation.""" |
| if precision is not torch.bfloat16: |
| raise ValueError("The mixed NVFP4 service requires BF16 exclusions") |
| if compile: |
| raise ValueError("torch.compile is not qualified for this checkpoint") |
| if max_length < 1024: |
| raise ValueError("NVFP4 API cache length must be at least 1024") |
|
|
| input_queue: queue.Queue = queue.Queue() |
| init_event = threading.Event() |
| init_error: list[BaseException] = [] |
|
|
| def worker() -> None: |
| try: |
| model = load_mixed_nvfp4_checkpoint( |
| checkpoint_path, |
| device=device, |
| max_length=max_length, |
| verify_checksums=verify_checksums, |
| ) |
| with torch.device(device): |
| model.setup_caches( |
| max_batch_size=1, |
| max_seq_len=model.config.max_seq_len, |
| dtype=torch.bfloat16, |
| ) |
| model._cache_setup_done = True |
| nvfp4_count = sum( |
| isinstance(module, NVFP4Linear) for module in model.modules() |
| ) |
| mxfp8_count = sum( |
| isinstance(module, MXFP8Linear) for module in model.modules() |
| ) |
| if (nvfp4_count, mxfp8_count) != (60, 120): |
| raise RuntimeError( |
| f"Expected 60/120 NVFP4/MXFP8 modules, got {nvfp4_count}/{mxfp8_count}" |
| ) |
| logger.info( |
| "Loaded standalone English NVFP4 checkpoint with " |
| f"{nvfp4_count} NVFP4 and {mxfp8_count} MXFP8 projections on {device}" |
| ) |
| except BaseException as error: |
| init_error.append(error) |
| logger.error(traceback.format_exc()) |
| init_event.set() |
| return |
|
|
| init_event.set() |
| while True: |
| item: GenerateRequest | None = input_queue.get() |
| if item is None: |
| break |
| response_queue = item.response_queue |
| try: |
| for chunk in generate_long( |
| model=model, |
| decode_one_token=decode_one_token_ar, |
| **item.request, |
| ): |
| response_queue.put( |
| WrappedGenerateResponse(status="success", response=chunk) |
| ) |
| except Exception as error: |
| logger.error(traceback.format_exc()) |
| response_queue.put( |
| WrappedGenerateResponse(status="error", response=error) |
| ) |
| finally: |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
|
|
| threading.Thread( |
| target=worker, |
| name="fish-s2-pro-mixed-nvfp4-worker", |
| daemon=True, |
| ).start() |
| init_event.wait() |
| if init_error: |
| raise RuntimeError("Mixed NVFP4 model worker failed to initialize") from init_error[0] |
| return input_queue |
|
|