| from __future__ import annotations |
|
|
| import json |
| import os |
| import queue |
| import threading |
| import time |
| from datetime import date |
| from pathlib import Path |
| from typing import Any |
|
|
|
|
| os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") |
| os.environ.setdefault("PYTORCH_ALLOC_CONF", "expandable_segments:True") |
| os.environ.setdefault("HF_DEACTIVATE_ASYNC_LOAD", "1") |
| try: |
| DEFAULT_COLD_START_DURATION_SECONDS = int(os.getenv("MUSE_COLD_START_DURATION_SECONDS", "120")) |
| except (TypeError, ValueError): |
| DEFAULT_COLD_START_DURATION_SECONDS = 120 |
|
|
| SKIP_MODEL_LOAD = os.getenv("MUSE_SKIP_MODEL_LOAD", "0") == "1" |
|
|
| try: |
| import spaces |
| except ModuleNotFoundError: |
| if not SKIP_MODEL_LOAD: |
| raise |
|
|
| class _LocalSpaces: |
| @staticmethod |
| def GPU(*_args, **_kwargs): |
| def decorator(function): |
| return function |
|
|
| return decorator |
|
|
| spaces = _LocalSpaces() |
|
|
| import gradio as gr |
| from PIL import Image, ImageOps |
| import torch |
| from transformers import StoppingCriteria, StoppingCriteriaList, TextIteratorStreamer |
|
|
| from muse_core import ( |
| APP_INPUT_TOKEN_LIMIT, |
| DEFAULT_MAX_NEW_TOKENS, |
| DEFAULT_REPETITION_PENALTY, |
| DEFAULT_SEED, |
| DEFAULT_TEMPERATURE, |
| DEFAULT_TOP_K, |
| DEFAULT_TOP_P, |
| MAX_NEW_TOKENS, |
| META_SAMPLING, |
| MODEL_CONTEXT_TOKENS, |
| NATIVE_GREEDY, |
| PRESETS, |
| choose_seed, |
| coerce_parsed_reply, |
| estimate_gpu_duration, |
| friendly_error, |
| generation_kwargs, |
| preset_values, |
| render_reply, |
| validate_controls, |
| ) |
|
|
|
|
| MODEL_ID = "meta-models/Muse-Glimmer-30B" |
| MODEL_REVISION = "f84ecc3a0ea984a4c04542a84269e3d065350a6e" |
| ASSISTANT_MODEL_ID = "meta-models/Muse-Glimmer-30B-assistant" |
| ASSISTANT_MODEL_REVISION = "2c86316d689027b91123638739743fef1d425233" |
| EXPECTED_MODEL_TYPE = "muse_glimmer" |
| EXPECTED_MODEL_TYPES = {EXPECTED_MODEL_TYPE, "muse_glimmer_assistant"} |
| ASSISTANT_EXPECTED_PARAMETER_COUNT = 2_555_985_152 |
|
|
| MODEL_CHOICES = [ |
| ("Muse Glimmer 30B (full BF16)", MODEL_ID), |
| ("Muse Glimmer 30B-assistant (compact)", ASSISTANT_MODEL_ID), |
| ] |
| SUBMIT_API_NAME = "chat_submit" |
| MODEL_NAME_ALIASES = { |
| "/Muse-Glimmer 30B": MODEL_ID, |
| "/Muse-Glimmer-30B": MODEL_ID, |
| "Muse Glimmer 30B (full BF16)": MODEL_ID, |
| "/Muse-Glimmer 30B-assistant": ASSISTANT_MODEL_ID, |
| "/Muse-Glimmer-30B-assistant": ASSISTANT_MODEL_ID, |
| "Muse Glimmer 30B-assistant (compact)": ASSISTANT_MODEL_ID, |
| "0": MODEL_ID, |
| "1": ASSISTANT_MODEL_ID, |
| "": MODEL_ID, |
| } |
| _BASE_MODEL_PATH = Path(os.getenv("MUSE_MODEL_PATH", "/models/muse-glimmer")) |
| _BASE_ASSISTANT_MODEL_PATH = Path( |
| os.getenv("MUSE_ASSISTANT_MODEL_PATH", "/models/muse-glimmer-assistant") |
| ) |
|
|
|
|
| def _has_model_manifest(path: Path) -> bool: |
| has_config = (path / "config.json").is_file() |
| if not has_config: |
| return False |
| return (path / "chat_template.jinja").is_file() or (path / "tokenizer.json").is_file() |
|
|
|
|
| def _resolve_mount_path(model_root: Path) -> Path: |
| """Handle both direct and mounted-directory layouts for model checkpoints.""" |
| try: |
| if _has_model_manifest(model_root): |
| return model_root |
| except OSError: |
| return model_root |
|
|
| if not model_root.is_dir(): |
| return model_root |
|
|
| |
| candidate_children = [] |
| try: |
| for child in model_root.iterdir(): |
| if child.is_dir() and _has_model_manifest(child): |
| candidate_children.append(child) |
| except OSError: |
| return model_root |
|
|
| if len(candidate_children) == 1: |
| return candidate_children[0] |
| if len(candidate_children) > 1: |
| |
| for child in candidate_children: |
| if child.name.startswith("Muse-Glimmer-30B"): |
| return child |
| return model_root |
|
|
|
|
| MODEL_REGISTRY = { |
| MODEL_ID: { |
| "revision": MODEL_REVISION, |
| "path": _resolve_mount_path(_BASE_MODEL_PATH), |
| "expected_model_type": "muse_glimmer", |
| "expected_parameter_count": 29_776_626_688, |
| "display": "Muse Glimmer 30B (full BF16)", |
| }, |
| ASSISTANT_MODEL_ID: { |
| "revision": ASSISTANT_MODEL_REVISION, |
| "path": _resolve_mount_path(_BASE_ASSISTANT_MODEL_PATH), |
| "expected_model_type": "muse_glimmer_assistant", |
| "expected_parameter_count": ASSISTANT_EXPECTED_PARAMETER_COUNT, |
| "display": "Muse Glimmer 30B-assistant (compact)", |
| }, |
| } |
|
|
| def _coerce_model_id(model_id: Any) -> str: |
| if model_id in (None, []): |
| return MODEL_DEFAULT_ID |
|
|
| if isinstance(model_id, (tuple, list)): |
| if not model_id: |
| return MODEL_DEFAULT_ID |
| if len(model_id) > 1 and isinstance(model_id[1], str): |
| return model_id[1] |
| if isinstance(model_id[0], str): |
| return _coerce_model_id(model_id[0]) |
| return MODEL_DEFAULT_ID |
| if isinstance(model_id, int): |
| choices = [value for _label, value in MODEL_CHOICES] |
| if 0 <= model_id < len(choices): |
| return choices[model_id] |
| return MODEL_DEFAULT_ID |
| if isinstance(model_id, str): |
| normalized = model_id.strip() |
| if normalized in MODEL_NAME_ALIASES: |
| return MODEL_NAME_ALIASES[normalized] |
| if model_id.isdigit(): |
| choices = [value for _label, value in MODEL_CHOICES] |
| idx = int(model_id) |
| if 0 <= idx < len(choices): |
| return choices[idx] |
| return model_id |
| return str(model_id) |
|
|
|
|
| def _resolve_default_model_id() -> str: |
| configured = os.getenv("MUSE_DEFAULT_MODEL_ID", MODEL_ID) |
| if configured not in MODEL_REGISTRY: |
| configured = MODEL_ID |
| configured_path = MODEL_REGISTRY[configured]["path"] |
| if configured_path.is_dir(): |
| return configured |
| for model_id, spec in MODEL_REGISTRY.items(): |
| if model_id == configured: |
| continue |
| if spec["path"].is_dir(): |
| return model_id |
| return configured |
|
|
|
|
| MODEL_DEFAULT_ID = _resolve_default_model_id() |
|
|
| MAX_HISTORY_MESSAGES = 20 |
| MAX_HISTORY_IMAGES = 2 |
| MAX_IMAGE_EDGE = 2_048 |
| MAX_IMAGE_PIXELS = 4_194_304 |
|
|
| ACTIVE_MODEL_ID: str | None = None |
| ACTIVE_MODEL = None |
| ACTIVE_PROCESSOR = None |
|
|
|
|
| def _model_spec(model_id: str) -> dict[str, Any]: |
| if model_id not in MODEL_REGISTRY: |
| raise ValueError(f"Unknown model selection: {model_id}") |
| return MODEL_REGISTRY[model_id] |
|
|
|
|
| def _is_model_checkpoint(path: str | os.PathLike[str], model_path: Path) -> bool: |
| try: |
| candidate = Path(path).resolve() |
| model_root = model_path.resolve() |
| except (OSError, RuntimeError, ValueError): |
| return False |
| candidate_text = str(candidate) |
| model_root_text = str(model_root) |
| return candidate == model_root or candidate_text.startswith(model_root_text + os.sep) |
|
|
|
|
| def _normalize_load_result(result: Any) -> tuple[Any, dict[str, Any]]: |
| if isinstance(result, tuple): |
| if len(result) >= 2: |
| return result[0], result[1] |
| return result[0], {} |
| if isinstance(result, dict): |
| return result.get("model"), result |
| return result, {} |
|
|
|
|
| def _supports_generation(model: Any) -> bool: |
| return callable(getattr(model, "generate", None)) |
|
|
|
|
| def _load_model_with_pread( |
| model_class, |
| model_path: Path, |
| *, |
| use_safetensors: bool = True, |
| safe_open_backend: str | None = "pread", |
| trust_remote_code: bool = False, |
| ): |
| """Load the mounted shards sequentially without mmap or whole-shard RAM copies. |
| |
| Transformers 5.15 deliberately disables mmap for Hugging Face model volumes because |
| concurrent page faults can deadlock hf-mount. Its fallback reads an entire safetensors |
| shard into host RAM; Muse Glimmer's first shard is about 50 GB, while a standard Space |
| has far less host RAM. Safetensors 0.8's pread backend avoids both failure modes and lets |
| Transformers materialize and dispatch one tensor at a time. |
| """ |
| from safetensors import safe_open as safetensors_safe_open |
| from transformers import modeling_utils |
|
|
| if not hasattr(modeling_utils, "_is_on_hf_mount") or not hasattr(modeling_utils, "safe_open"): |
| return _load_model_direct(model_class, model_path) |
|
|
| shards = sorted(model_path.glob("*.safetensors")) |
| if not shards: |
| raise RuntimeError("The mounted checkpoint contains no safetensors shards.") |
|
|
| |
| safe_open_kwargs = {"framework": "pt", "device": "cpu"} |
| if safe_open_backend is not None: |
| safe_open_kwargs["backend"] = safe_open_backend |
| with safetensors_safe_open(str(shards[0]), **safe_open_kwargs) as checkpoint: |
| first_key = next(iter(checkpoint.keys()), None) |
| if first_key is None: |
| raise RuntimeError("The mounted safetensors checkpoint is empty.") |
| checkpoint.get_slice(first_key).get_shape() |
|
|
| original_mount_check = modeling_utils._is_on_hf_mount |
| original_safe_open = modeling_utils.safe_open |
|
|
| def model_mount_check(path): |
| if _is_model_checkpoint(path, model_path): |
| return False |
| return original_mount_check(path) |
|
|
| def model_safe_open(path, *args, **kwargs): |
| if _is_model_checkpoint(path, model_path) and os.fspath(path).endswith(".safetensors"): |
| if safe_open_backend is not None: |
| kwargs["backend"] = safe_open_backend |
| return original_safe_open(path, *args, **kwargs) |
|
|
| modeling_utils._is_on_hf_mount = model_mount_check |
| modeling_utils.safe_open = model_safe_open |
| try: |
| loaded = model_class.from_pretrained( |
| model_path, |
| dtype=torch.bfloat16, |
| device_map={"": "cuda"}, |
| local_files_only=True, |
| trust_remote_code=trust_remote_code, |
| attn_implementation="sdpa", |
| output_loading_info=True, |
| disable_mmap=False, |
| use_safetensors=use_safetensors, |
| ) |
| return _normalize_load_result(loaded) |
| finally: |
| modeling_utils._is_on_hf_mount = original_mount_check |
| modeling_utils.safe_open = original_safe_open |
|
|
|
|
| def _load_model_direct( |
| model_class, |
| model_path: Path, |
| *, |
| use_safetensors: bool = True, |
| trust_remote_code: bool = False, |
| ): |
| return _normalize_load_result( |
| model_class.from_pretrained( |
| model_path, |
| dtype=torch.bfloat16, |
| device_map={"": "cuda"}, |
| local_files_only=True, |
| trust_remote_code=trust_remote_code, |
| attn_implementation="sdpa", |
| output_loading_info=True, |
| use_safetensors=use_safetensors, |
| ) |
| ) |
|
|
|
|
| def _load_model_candidate( |
| model_class, |
| model_path: Path, |
| *, |
| trust_remote_code: bool, |
| ): |
| for use_safetensors in (True, False): |
| for safe_open_backend in ("pread", "read", None): |
| try: |
| return _load_model_with_pread( |
| model_class, |
| model_path, |
| use_safetensors=use_safetensors, |
| safe_open_backend=safe_open_backend, |
| trust_remote_code=trust_remote_code, |
| ) |
| except Exception: |
| pass |
| return _load_model_direct(model_class, model_path, use_safetensors=False, trust_remote_code=trust_remote_code) |
|
|
|
|
| def _load_model_candidate_or_remote( |
| model_class, |
| spec: dict[str, Any], |
| model_id: str, |
| *, |
| trust_remote_code: bool, |
| ): |
| model_path = spec["path"] |
| revision = spec["revision"] |
| try: |
| return _load_model_candidate( |
| model_class, |
| model_path, |
| trust_remote_code=trust_remote_code, |
| ) |
| except Exception: |
| pass |
|
|
| for use_safetensors in (True, False): |
| try: |
| return _normalize_load_result( |
| model_class.from_pretrained( |
| model_id, |
| revision=revision, |
| dtype=torch.bfloat16, |
| device_map={"": "cuda"}, |
| local_files_only=False, |
| trust_remote_code=trust_remote_code, |
| attn_implementation="sdpa", |
| output_loading_info=True, |
| use_safetensors=use_safetensors, |
| cache_dir="/tmp/huggingface-model-cache", |
| ) |
| ) |
| except Exception: |
| pass |
| raise RuntimeError("Unable to load the selected checkpoint from local mount or remote Hub download.") |
|
|
|
|
| def _load_runtime(model_id: str): |
| spec = _model_spec(model_id) |
| model_path = spec["path"] |
| revision = spec["revision"] |
| expected_model_type = spec["expected_model_type"] |
| has_mount = model_path.is_dir() |
| has_assistant_fallback_mount = MODEL_REGISTRY[MODEL_ID]["path"].is_dir() |
| use_remote = not has_mount and model_id == ASSISTANT_MODEL_ID |
| if not has_mount and not use_remote: |
| raise RuntimeError( |
| f"The selected Muse Glimmer full model mount is missing at {model_path}. " |
| "Attach the read-only model volume before starting the Space." |
| ) |
|
|
| from transformers import AutoConfig, AutoModelForCausalLM, AutoProcessor, AutoTokenizer |
|
|
| print( |
| f"[startup] Loading processor from " |
| f"{'model repository' if use_remote else model_path} ({revision[:12]}…).", |
| flush=True, |
| ) |
| source = model_id if use_remote else model_path |
| processor_kwargs = { |
| "revision": revision, |
| "local_files_only": not use_remote, |
| "trust_remote_code": False, |
| } |
| config_kwargs = { |
| "revision": revision, |
| "local_files_only": not use_remote, |
| "trust_remote_code": False, |
| } |
| if model_id == ASSISTANT_MODEL_ID: |
| processor_source = ( |
| MODEL_REGISTRY[MODEL_ID]["path"] if has_assistant_fallback_mount else MODEL_ID |
| ) |
| if processor_source == MODEL_REGISTRY[MODEL_ID]["path"]: |
| processor_kwargs["local_files_only"] = True |
| processor_kwargs["revision"] = MODEL_REVISION |
| else: |
| processor_kwargs["local_files_only"] = False |
| processor_kwargs["revision"] = MODEL_REVISION |
| print( |
| "[startup] Assistant-selected checkpoint will reuse " |
| f"base-tokenization assets from `{processor_source}`.", |
| flush=True, |
| ) |
| else: |
| processor_source = source |
|
|
| if use_remote: |
| processor_kwargs["cache_dir"] = "/tmp/huggingface-model-cache" |
| config_kwargs["cache_dir"] = "/tmp/huggingface-model-cache" |
|
|
| if model_id == ASSISTANT_MODEL_ID: |
| try: |
| processor = AutoProcessor.from_pretrained(processor_source, **processor_kwargs) |
| tokenizer = AutoTokenizer.from_pretrained(processor_source, **processor_kwargs) |
| except Exception: |
| print( |
| "[startup] Processor loading failed without trust_remote_code; retrying with trust_remote_code=True.", |
| flush=True, |
| ) |
| fallback_processor_kwargs = dict(processor_kwargs) |
| fallback_processor_kwargs["trust_remote_code"] = True |
| processor = AutoProcessor.from_pretrained(processor_source, **fallback_processor_kwargs) |
| tokenizer = AutoTokenizer.from_pretrained(processor_source, **fallback_processor_kwargs) |
| if not hasattr(processor, "tokenizer"): |
| processor.tokenizer = tokenizer |
| else: |
| try: |
| processor = AutoProcessor.from_pretrained(source, **processor_kwargs) |
| except Exception: |
| print( |
| "[startup] Processor loading failed without trust_remote_code; retrying with trust_remote_code=True.", |
| flush=True, |
| ) |
| fallback_processor_kwargs = dict(processor_kwargs) |
| fallback_processor_kwargs["trust_remote_code"] = True |
| processor = AutoProcessor.from_pretrained(source, **fallback_processor_kwargs) |
|
|
| try: |
| config = AutoConfig.from_pretrained(source, **config_kwargs) |
| except Exception: |
| config = None |
| if config is None: |
| fallback_config_kwargs = dict(config_kwargs) |
| fallback_config_kwargs["trust_remote_code"] = True |
| try: |
| config = AutoConfig.from_pretrained(source, **fallback_config_kwargs) |
| print( |
| "[startup] AutoConfig with trust_remote_code succeeded for the selected checkpoint.", |
| flush=True, |
| ) |
| except Exception as error: |
| raise RuntimeError("Unable to load model configuration from the selected checkpoint.") from error |
| model_type = getattr(config, "model_type", None) |
| if model_type != expected_model_type: |
| print( |
| f"[startup] Warning: checkpoint model_type={model_type} while expected {expected_model_type}. " |
| "Proceeding with detected architecture checks.", |
| flush=True, |
| ) |
|
|
| if model_type == "muse_glimmer": |
| from transformers import MuseGlimmerForConditionalGeneration |
|
|
| model_candidates = ((MuseGlimmerForConditionalGeneration, False),) |
| elif model_type == "muse_glimmer_assistant": |
| try: |
| from transformers.models.muse_glimmer_assistant.modeling_muse_glimmer_assistant import ( |
| MuseGlimmerAssistantModel, |
| ) |
|
|
| model_candidates = ( |
| (MuseGlimmerAssistantModel, False), |
| (MuseGlimmerAssistantModel, True), |
| ) |
| except Exception: |
| model_candidates = ( |
| (AutoModelForCausalLM, False), |
| (AutoModelForCausalLM, True), |
| ) |
| else: |
| raise RuntimeError( |
| f"Unsupported model type from checkpoint: {model_type}. " |
| f"Expected {expected_model_type or 'a Muse Glimmer variant'}." |
| ) |
|
|
| print("[startup] Loading the selected Muse Glimmer checkpoint onto ZeroGPU.", flush=True) |
| loading_info = {} |
| loading_error = None |
| model = None |
| used_model_class = None |
| try: |
| for model_class, trust_remote_code in model_candidates: |
| used_model_class = getattr(model_class, "__name__", str(model_class)) |
| try: |
| if model_type == "muse_glimmer_assistant": |
| print( |
| f"[startup] Trying {used_model_class} for assistant checkpoint " |
| f"with trust_remote_code={trust_remote_code}.", |
| flush=True, |
| ) |
| model, loading_info = _load_model_candidate_or_remote( |
| model_class, |
| spec, |
| model_id, |
| trust_remote_code=trust_remote_code, |
| ) |
| loading_error = None |
| break |
| except Exception as error: |
| loading_error = error |
| print( |
| f"[startup] {used_model_class} load failed ({type(error).__name__}); trying next option if available.", |
| flush=True, |
| ) |
| if model is None: |
| raise RuntimeError(f"No compatible loader could initialize model class for `{model_id}`.") |
| except Exception as error: |
| if loading_error is None: |
| loading_error = error |
| raise |
| if not isinstance(loading_info, dict): |
| loading_info = {} |
| loading_failures = { |
| key: loading_info.get(key) |
| for key in ( |
| "missing_keys", |
| "unexpected_keys", |
| "mismatched_keys", |
| "conversion_errors", |
| "error_msgs", |
| ) |
| if loading_info.get(key) |
| } |
| if loading_failures: |
| raise RuntimeError( |
| "The pinned checkpoint did not load cleanly: " |
| + ", ".join(f"{key}={len(value)}" for key, value in loading_failures.items()) |
| ) |
| if model_type == "muse_glimmer_assistant" and not _supports_generation(model): |
| print( |
| "[startup] Loaded assistant checkpoint is not a standalone generator; inference will fallback " |
| "to the full model at request time when selected.", |
| flush=True, |
| ) |
|
|
| loaded_model_type = getattr(model.config, "model_type", None) |
| if loaded_model_type is not None and loaded_model_type not in EXPECTED_MODEL_TYPES: |
| raise RuntimeError("The selected checkpoint is not a Muse Glimmer model.") |
| if loaded_model_type is None: |
| print("[startup] Checkpoint config has no model_type; proceeding with expected loader class.", flush=True) |
|
|
| parameter_count = sum(parameter.numel() for parameter in model.parameters()) |
| expected_parameter_count = spec["expected_parameter_count"] |
| if expected_parameter_count is not None and parameter_count != expected_parameter_count: |
| raise RuntimeError( |
| f"Unexpected parameter count: {parameter_count:,}; expected {expected_parameter_count:,}." |
| ) |
|
|
| model.eval() |
| if loading_error is not None: |
| print(f"[startup] Loaded with fallback loader after: {type(loading_error).__name__}", flush=True) |
| print( |
| f"[startup] Ready: {parameter_count:,} parameters from `{model_id}` ({revision[:12]}…).", |
| flush=True, |
| ) |
| return processor, model |
|
|
|
|
| def _activate_model(model_id: str): |
| global ACTIVE_MODEL_ID, ACTIVE_MODEL, ACTIVE_PROCESSOR, PROCESSOR, MODEL |
|
|
| if model_id not in MODEL_REGISTRY: |
| raise ValueError(f"Unknown model selection: {model_id}") |
|
|
| if ACTIVE_MODEL_ID == model_id and ACTIVE_MODEL is not None and ACTIVE_PROCESSOR is not None: |
| return ACTIVE_MODEL, ACTIVE_PROCESSOR |
|
|
| if ACTIVE_MODEL is not None: |
| del ACTIVE_MODEL |
| if ACTIVE_PROCESSOR is not None: |
| del ACTIVE_PROCESSOR |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
|
|
| ACTIVE_PROCESSOR, ACTIVE_MODEL = _load_runtime(model_id) |
| ACTIVE_MODEL_ID = model_id |
| if torch.cuda.is_available(): |
| torch.cuda.synchronize() |
| PROCESSOR = ACTIVE_PROCESSOR |
| MODEL = ACTIVE_MODEL |
| return ACTIVE_PROCESSOR, ACTIVE_MODEL |
|
|
|
|
| if SKIP_MODEL_LOAD: |
| PROCESSOR = None |
| MODEL = None |
| else: |
| available_models = [model_id for model_id, spec in MODEL_REGISTRY.items() if spec["path"].is_dir()] |
| if available_models: |
| print( |
| f"[startup] Model loading deferred until first request. Available mounts: {', '.join(available_models)}", |
| flush=True, |
| ) |
| else: |
| print("[startup] No checkpoint mounts are available at startup; model loading is deferred.", flush=True) |
|
|
| PROCESSOR = None |
| MODEL = None |
|
|
|
|
| class _StopOnEvent(StoppingCriteria): |
| def __init__(self, event: threading.Event): |
| self.event = event |
|
|
| def __call__(self, input_ids, scores, **kwargs): |
| del scores, kwargs |
| return torch.full( |
| (input_ids.shape[0],), |
| self.event.is_set(), |
| dtype=torch.bool, |
| device=input_ids.device, |
| ) |
|
|
|
|
| def _coerce_image_input(image: Any) -> Image.Image | None: |
| if image is None or (isinstance(image, str) and not image): |
| return None |
| if not isinstance(image, Image.Image): |
| raise ValueError("The image upload could not be decoded.") |
| return _normalize_image(image) |
|
|
|
|
| def _normalize_image(image: Image.Image | None) -> Image.Image | None: |
| width, height = image.size |
| if width < 1 or height < 1: |
| raise ValueError("The image has invalid dimensions.") |
| if width * height > MAX_IMAGE_PIXELS: |
| scale = (MAX_IMAGE_PIXELS / float(width * height)) ** 0.5 |
| image = image.resize( |
| (max(1, int(width * scale)), max(1, int(height * scale))), |
| Image.Resampling.LANCZOS, |
| ) |
| image = ImageOps.exif_transpose(image) |
| image.thumbnail((MAX_IMAGE_EDGE, MAX_IMAGE_EDGE), Image.Resampling.LANCZOS) |
| clean = Image.new("RGB", image.size) |
| if image.mode == "RGBA": |
| background = Image.new("RGBA", image.size, "white") |
| background.alpha_composite(image) |
| clean.paste(background.convert("RGB")) |
| else: |
| clean.paste(image.convert("RGB")) |
| return clean |
|
|
|
|
| def _response_tokenizer_for(obj: Any): |
| tokenizer = getattr(obj, "tokenizer", None) |
| if tokenizer is not None: |
| return tokenizer |
| return getattr(obj, "_tokenizer", None) |
|
|
|
|
| def _coerce_chat_objects(processor_or_tokenizer: Any, model_id: str) -> tuple[Any, Any]: |
| """Return a processor/tokenizer pair that both support templating and parser wiring. |
| |
| This guards against edge cases where processor loading returns an unexpected object |
| (for example during Transformers internals or runtime cache fallback behavior). |
| """ |
| from transformers import AutoProcessor, AutoTokenizer |
|
|
| spec = _model_spec(model_id) |
| source = spec["path"] if spec["path"].is_dir() else model_id |
| base_kwargs = { |
| "revision": spec["revision"], |
| "local_files_only": source == spec["path"] and spec["path"].is_dir(), |
| "trust_remote_code": False, |
| } |
|
|
| candidates: list[Any] = [processor_or_tokenizer] |
| tokenized = _response_tokenizer_for(processor_or_tokenizer) |
| if tokenized is not None: |
| candidates.append(tokenized) |
|
|
| def _supports_template(candidate: Any) -> bool: |
| return candidate is not None and hasattr(candidate, "apply_chat_template") |
|
|
| def _valid(candidate: Any) -> bool: |
| return _supports_template(candidate) and hasattr(candidate, "get_response_parser") |
|
|
| for candidate in candidates: |
| if candidate is not None and _valid(candidate): |
| return candidate, _response_tokenizer_for(candidate) or candidate |
|
|
| for candidate in candidates: |
| if _supports_template(candidate): |
| return candidate, _response_tokenizer_for(candidate) or candidate |
|
|
| for trust_remote_code in (False, True): |
| fallback_kwargs = dict(base_kwargs) |
| fallback_kwargs["trust_remote_code"] = trust_remote_code |
| try: |
| candidate = AutoProcessor.from_pretrained(source, **fallback_kwargs) |
| if _valid(candidate): |
| return candidate, _response_tokenizer_for(candidate) or candidate |
| except Exception: |
| pass |
| try: |
| candidate = AutoTokenizer.from_pretrained(source, **fallback_kwargs) |
| if _valid(candidate): |
| return candidate, candidate |
| if _supports_template(candidate): |
| return candidate, candidate |
| except Exception: |
| pass |
|
|
| raise RuntimeError("Unable to initialize chat template/parser components for the selected model.") |
|
|
|
|
| def _parse_llm_response(text: str | None) -> tuple[str, str]: |
| text = (text or "").strip() |
| if not text: |
| return "", "" |
|
|
| think_open = "<think>" |
| think_close = "</think>" |
| start = text.find(think_open) |
| if start == -1: |
| return "", text |
|
|
| start += len(think_open) |
| close = text.find(think_close, start) |
| if close == -1: |
| return text[start:].strip(), "" |
|
|
| reasoning = text[start:close].strip() |
| content = text[close + len(think_close) :].strip() |
| return reasoning, content |
|
|
|
|
| def _user_content(prompt: str, image: Image.Image | None): |
| if image is None: |
| return prompt |
| return [ |
| {"type": "image", "image": image}, |
| {"type": "text", "text": prompt}, |
| ] |
|
|
|
|
| def _visible_user_message(prompt: str, image: Image.Image | None) -> str: |
| if image is None: |
| return prompt |
| return f"{prompt}\n\n_🖼️ Image attached to this turn._" |
|
|
|
|
| def _clean_model_history(history) -> list[dict[str, Any]]: |
| cleaned: list[dict[str, Any]] = [] |
| for message in list(history or [])[-MAX_HISTORY_MESSAGES:]: |
| if not isinstance(message, dict) or message.get("role") not in {"user", "assistant"}: |
| continue |
| if "content" not in message: |
| continue |
| safe = {"role": message["role"], "content": message["content"]} |
| if message["role"] == "assistant" and isinstance(message.get("reasoning_content"), str): |
| safe["reasoning_content"] = message["reasoning_content"] |
| cleaned.append(safe) |
| if cleaned and cleaned[0]["role"] == "assistant": |
| cleaned.pop(0) |
|
|
| |
| |
| kept_images = 0 |
| for message in reversed(cleaned): |
| content = message.get("content") |
| if message.get("role") != "user" or not isinstance(content, list): |
| continue |
| has_image = any(isinstance(part, dict) and part.get("type") == "image" for part in content) |
| if not has_image: |
| continue |
| kept_images += 1 |
| if kept_images <= MAX_HISTORY_IMAGES: |
| continue |
| text_parts = [ |
| part.get("text", "") |
| for part in content |
| if isinstance(part, dict) and part.get("type") == "text" |
| ] |
| message["content"] = "\n".join(part for part in text_parts if part).strip() |
| return cleaned |
|
|
|
|
| def _apply_template(processor, messages: list[dict[str, Any]], reasoning_strength: str): |
| return processor.apply_chat_template( |
| messages, |
| tokenize=True, |
| add_generation_prompt=True, |
| reasoning_strength=reasoning_strength, |
| current_date=date.today().isoformat(), |
| return_dict=True, |
| return_tensors="pt", |
| ) |
|
|
|
|
| def _prepare_inputs( |
| processor, |
| model_history, |
| prompt: str, |
| image: Image.Image | None, |
| system_prompt: str, |
| reasoning_strength: str, |
| max_new_tokens: int, |
| ): |
| retained = _clean_model_history(model_history) |
| current_user = {"role": "user", "content": _user_content(prompt, image)} |
| trimmed_messages = 0 |
|
|
| while True: |
| messages: list[dict[str, Any]] = [] |
| if system_prompt.strip(): |
| messages.append({"role": "system", "content": system_prompt.strip()}) |
| messages.extend(retained) |
| messages.append(current_user) |
| encoded = _apply_template(processor, messages, reasoning_strength) |
| input_tokens = int(encoded["input_ids"].shape[-1]) |
| if input_tokens <= APP_INPUT_TOKEN_LIMIT: |
| break |
| if not retained: |
| raise ValueError( |
| f"The current turn exceeds the app input limit of {APP_INPUT_TOKEN_LIMIT:,} tokens." |
| ) |
| retained.pop(0) |
| trimmed_messages += 1 |
| if retained and retained[0].get("role") == "assistant": |
| retained.pop(0) |
| trimmed_messages += 1 |
|
|
| if input_tokens + int(max_new_tokens) > MODEL_CONTEXT_TOKENS: |
| raise ValueError("The prompt and response budget exceed the model context window.") |
| return retained, current_user, encoded, input_tokens, trimmed_messages |
|
|
|
|
| def _move_inputs_to_model(model, encoded): |
| device = next(model.parameters()).device |
| moved = {} |
| for key, value in encoded.items(): |
| if not torch.is_tensor(value): |
| moved[key] = value |
| continue |
| value = value.to(device) |
| if key in {"pixel_values", "pixel_values_videos"} and value.is_floating_point(): |
| value = value.to(dtype=torch.bfloat16) |
| moved[key] = value |
| return moved |
|
|
|
|
| def _gpu_duration( |
| prompt, |
| image, |
| selected_model, |
| chat_history, |
| model_history, |
| system_prompt, |
| reasoning_strength, |
| do_sample, |
| max_new_tokens, |
| temperature, |
| top_p, |
| top_k, |
| repetition_penalty, |
| seed, |
| randomize_seed, |
| show_reasoning, |
| ): |
| _ = ( |
| prompt, |
| chat_history, |
| system_prompt, |
| do_sample, |
| temperature, |
| top_p, |
| top_k, |
| repetition_penalty, |
| seed, |
| randomize_seed, |
| show_reasoning, |
| ) |
|
|
| selected_model = _coerce_model_id(selected_model) or MODEL_DEFAULT_ID |
| try: |
| max_new_tokens = int(max_new_tokens) |
| except Exception: |
| max_new_tokens = DEFAULT_MAX_NEW_TOKENS |
|
|
| has_image = False |
| try: |
| has_image = _coerce_image_input(image) is not None |
| except ValueError: |
| has_image = False |
|
|
| needs_warmup = selected_model != ACTIVE_MODEL_ID |
| estimated = estimate_gpu_duration(max_new_tokens, has_image) |
| if selected_model == MODEL_ID and needs_warmup: |
| return min(estimated, DEFAULT_COLD_START_DURATION_SECONDS) |
| if selected_model == ASSISTANT_MODEL_ID: |
| if ACTIVE_MODEL_ID == MODEL_ID: |
| return estimated |
| return min(estimated, DEFAULT_COLD_START_DURATION_SECONDS) |
| return estimated |
|
|
|
|
| def _format_status( |
| *, |
| phase: str, |
| selected_model: str, |
| input_tokens: int, |
| output_tokens: int, |
| elapsed: float, |
| used_seed: int, |
| do_sample: bool, |
| trimmed_messages: int, |
| ) -> str: |
| mode = "sampling" if do_sample else "native greedy" |
| trimmed = f" · trimmed {trimmed_messages} old messages" if trimmed_messages else "" |
| return ( |
| f"{phase} · {input_tokens:,} input / {output_tokens:,} output tokens · " |
| f"{elapsed:.1f}s · {mode} · seed {used_seed}{trimmed} · {selected_model}" |
| ) |
|
|
|
|
| @spaces.GPU(size="xlarge", duration=_gpu_duration) |
| def _generate_turn( |
| prompt, |
| image, |
| selected_model, |
| chat_history, |
| model_history, |
| system_prompt, |
| reasoning_strength, |
| do_sample, |
| max_new_tokens, |
| temperature, |
| top_p, |
| top_k, |
| repetition_penalty, |
| seed, |
| randomize_seed, |
| show_reasoning, |
| ): |
| original_chat = list(chat_history or []) |
| original_model_history = list(model_history or []) |
| generation_thread: threading.Thread | None = None |
| stop_event = threading.Event() |
|
|
| try: |
| selected_model = _coerce_model_id(selected_model) or MODEL_DEFAULT_ID |
| selected_model_name = _model_spec(selected_model).get("display", selected_model) |
| active_inference_model = selected_model |
| model_fallback = False |
| processor, model = _activate_model(selected_model) |
| if active_inference_model == ASSISTANT_MODEL_ID and not _supports_generation(model): |
| print( |
| "[inference] Assistant checkpoint does not expose generate(); falling back to full model for this request.", |
| flush=True, |
| ) |
| model_fallback = True |
| active_inference_model = MODEL_ID |
| processor, model = _activate_model(active_inference_model) |
| selected_model_name = _model_spec(MODEL_ID).get("display", MODEL_ID) |
| if processor is None or model is None: |
| raise RuntimeError("Model loading is unavailable for this request.") |
| prompt = (prompt or "").strip() |
| if not prompt: |
| raise ValueError("Write a prompt before generating.") |
| if len(prompt) > 20_000: |
| raise ValueError("The prompt is too long; keep it below 20,000 characters.") |
|
|
| max_new_tokens = int(DEFAULT_MAX_NEW_TOKENS if max_new_tokens is None else max_new_tokens) |
| temperature = DEFAULT_TEMPERATURE if temperature is None else float(temperature) |
| top_p = DEFAULT_TOP_P if top_p is None else float(top_p) |
| top_k = DEFAULT_TOP_K if top_k is None else int(top_k) |
| repetition_penalty = ( |
| DEFAULT_REPETITION_PENALTY |
| if repetition_penalty is None |
| else float(repetition_penalty) |
| ) |
| validate_controls( |
| max_new_tokens=max_new_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| top_k=top_k, |
| repetition_penalty=repetition_penalty, |
| reasoning_strength=reasoning_strength, |
| ) |
| used_seed = choose_seed(seed, bool(randomize_seed)) |
| clean_image = _coerce_image_input(image) |
| processor, response_parser_tokenizer = _coerce_chat_objects( |
| processor, active_inference_model |
| ) |
| retained, current_user, encoded, input_tokens, trimmed_messages = _prepare_inputs( |
| processor, |
| original_model_history, |
| prompt, |
| clean_image, |
| system_prompt or "", |
| reasoning_strength, |
| max_new_tokens, |
| ) |
|
|
| model_inputs = _move_inputs_to_model(model, encoded) |
| input_length = int(model_inputs["input_ids"].shape[-1]) |
| prefix_ids = encoded["input_ids"][0].detach().cpu() |
|
|
| torch.manual_seed(used_seed) |
| torch.cuda.manual_seed_all(used_seed) |
|
|
| streamer = TextIteratorStreamer( |
| response_parser_tokenizer, |
| skip_prompt=True, |
| skip_special_tokens=False, |
| timeout=5.0, |
| ) |
| parser = ( |
| response_parser_tokenizer.get_response_parser(prefix=prefix_ids) |
| if hasattr(response_parser_tokenizer, "get_response_parser") |
| else None |
| ) |
| buffers = {"reasoning_content": "", "content": ""} |
| streamed_chunks: list[str] = [] |
| if parser is not None: |
| for event in parser.initial_events: |
| if event.get("type") == "region_chunk" and event.get("field") in buffers: |
| buffers[event["field"]] += event.get("text", "") |
|
|
| kwargs = { |
| **model_inputs, |
| **generation_kwargs( |
| do_sample=bool(do_sample), |
| max_new_tokens=max_new_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| top_k=top_k, |
| repetition_penalty=repetition_penalty, |
| ), |
| "streamer": streamer, |
| "stopping_criteria": StoppingCriteriaList([_StopOnEvent(stop_event)]), |
| "max_time": float(max(30, estimate_gpu_duration(max_new_tokens, clean_image is not None))), |
| } |
| errors: list[BaseException] = [] |
| result_box: list[Any] = [] |
|
|
| def run_model() -> None: |
| try: |
| with torch.inference_mode(): |
| result_box.append(model.generate(**kwargs)) |
| except BaseException as error: |
| errors.append(error) |
| streamer.on_finalized_text("", stream_end=True) |
|
|
| generation_thread = threading.Thread(target=run_model, daemon=True) |
| started = time.perf_counter() |
| generation_thread.start() |
|
|
| user_message = {"role": "user", "content": _visible_user_message(prompt, clean_image)} |
| working_chat = original_chat + [user_message] |
| last_yield = 0.0 |
|
|
| while True: |
| try: |
| chunk = next(streamer) |
| except queue.Empty: |
| if not generation_thread.is_alive(): |
| if errors: |
| break |
| raise RuntimeError("The generation stream ended unexpectedly.") |
| now = time.perf_counter() |
| yield ( |
| working_chat |
| + [ |
| { |
| "role": "assistant", |
| "content": render_reply( |
| buffers["reasoning_content"], |
| buffers["content"], |
| show_reasoning=bool(show_reasoning), |
| pending=True, |
| ), |
| } |
| ], |
| gr.skip(), |
| gr.skip(), |
| gr.skip(), |
| gr.skip(), |
| _format_status( |
| selected_model=( |
| f"{selected_model_name} (assistant checkpoint fallback to full model)" |
| if model_fallback |
| else selected_model_name |
| ), |
| phase="Generating", |
| input_tokens=input_tokens, |
| output_tokens=0, |
| elapsed=now - started, |
| used_seed=used_seed, |
| do_sample=bool(do_sample), |
| trimmed_messages=trimmed_messages, |
| ), |
| ) |
| last_yield = now |
| continue |
| except StopIteration: |
| break |
|
|
| if parser is not None: |
| for event in parser.feed(chunk): |
| field = event.get("field") |
| if field not in buffers: |
| continue |
| if event.get("type") == "region_chunk": |
| buffers[field] += event.get("text", "") |
| elif event.get("type") == "region_close" and isinstance(event.get("value"), str): |
| buffers[field] = event["value"] |
| else: |
| streamed_chunks.append(chunk) |
| reasoning, content = _parse_llm_response("".join(streamed_chunks)) |
| buffers["reasoning_content"] = reasoning |
| buffers["content"] = content |
|
|
| now = time.perf_counter() |
| if now - last_yield < 0.06: |
| continue |
| partial = render_reply( |
| buffers["reasoning_content"], |
| buffers["content"], |
| show_reasoning=bool(show_reasoning), |
| pending=True, |
| ) |
| elapsed = now - started |
| yield ( |
| working_chat + [{"role": "assistant", "content": partial}], |
| gr.skip(), |
| gr.skip(), |
| gr.skip(), |
| gr.skip(), |
| _format_status( |
| selected_model=( |
| f"{selected_model_name} (assistant checkpoint fallback to full model)" |
| if model_fallback |
| else selected_model_name |
| ), |
| phase="Generating", |
| input_tokens=input_tokens, |
| output_tokens=0, |
| elapsed=elapsed, |
| used_seed=used_seed, |
| do_sample=bool(do_sample), |
| trimmed_messages=trimmed_messages, |
| ), |
| ) |
| last_yield = now |
|
|
| generation_thread.join(timeout=3) |
| if generation_thread.is_alive(): |
| raise RuntimeError( |
| "Generation exceeded its timeout envelope. " |
| "Lower the response budget and try again." |
| ) |
| if errors: |
| raise errors[0] |
|
|
| if parser is not None: |
| parsed_message, final_events = parser.finalize() |
| for event in final_events: |
| field = event.get("field") |
| if ( |
| field in buffers |
| and event.get("type") == "region_close" |
| and isinstance(event.get("value"), str) |
| ): |
| buffers[field] = event["value"] |
| parsed = coerce_parsed_reply(parsed_message) |
| reasoning = parsed.reasoning or buffers["reasoning_content"].strip() |
| content = parsed.content or buffers["content"].strip() |
| else: |
| reasoning, content = _parse_llm_response("".join(streamed_chunks)) |
|
|
| output_tokens = 0 |
| ended_with_limit = False |
| if result_box: |
| generated = result_box[0] |
| output_tokens = int(generated.shape[-1]) - input_length |
| ended_with_limit = output_tokens >= max_new_tokens |
| if not reasoning and not content: |
| raise RuntimeError("The model returned no visible response fields.") |
|
|
| visible_reply = render_reply( |
| reasoning, |
| content, |
| show_reasoning=bool(show_reasoning), |
| hit_token_limit=ended_with_limit, |
| ) |
| assistant_state = {"role": "assistant", "content": content} |
| if reasoning: |
| assistant_state["reasoning_content"] = reasoning |
| updated_model_history = retained + [current_user, assistant_state] |
| updated_chat = working_chat + [{"role": "assistant", "content": visible_reply}] |
| elapsed = time.perf_counter() - started |
|
|
| yield ( |
| updated_chat, |
| updated_model_history, |
| updated_chat, |
| "", |
| None, |
| _format_status( |
| selected_model=( |
| f"{selected_model_name} (assistant checkpoint fallback to full model)" |
| if model_fallback |
| else selected_model_name |
| ), |
| phase="Complete", |
| input_tokens=input_tokens, |
| output_tokens=output_tokens, |
| elapsed=elapsed, |
| used_seed=used_seed, |
| do_sample=bool(do_sample), |
| trimmed_messages=trimmed_messages, |
| ), |
| ) |
| except GeneratorExit: |
| raise |
| except BaseException as error: |
| print(f"[inference] {type(error).__name__}: {error}", flush=True) |
| if torch.cuda.is_available(): |
| torch.cuda.empty_cache() |
| yield ( |
| original_chat, |
| gr.skip(), |
| original_chat, |
| gr.skip(), |
| gr.skip(), |
| f"Error · {friendly_error(error)}", |
| ) |
| finally: |
| stop_event.set() |
| if generation_thread is not None and generation_thread.is_alive(): |
| generation_thread.join(timeout=3) |
| if generation_thread.is_alive(): |
| print("[inference] Generation worker did not stop within grace window.", flush=True) |
|
|
|
|
| def _validate_generation_request( |
| prompt, |
| image, |
| selected_model, |
| chat_history, |
| model_history, |
| system_prompt, |
| reasoning_strength, |
| do_sample, |
| max_new_tokens, |
| temperature, |
| top_p, |
| top_k, |
| repetition_penalty, |
| seed, |
| randomize_seed, |
| show_reasoning, |
| ): |
| del chat_history, model_history, do_sample, show_reasoning |
| valid = True |
| message = "" |
| try: |
| selected_model = _coerce_model_id(selected_model) or MODEL_DEFAULT_ID |
| spec = _model_spec(selected_model) |
| max_new_tokens = 32 if max_new_tokens is None else int(max_new_tokens) |
| temperature = 1.0 if temperature is None else float(temperature) |
| top_p = 0.95 if top_p is None else float(top_p) |
| top_k = 64 if top_k is None else int(top_k) |
| repetition_penalty = 1.0 if repetition_penalty is None else float(repetition_penalty) |
| image = _coerce_image_input(image) |
| if not spec["path"].is_dir() and selected_model != ASSISTANT_MODEL_ID: |
| raise ValueError(f"The selected model checkpoint is not mounted at {spec['path']}.") |
| if selected_model == ASSISTANT_MODEL_ID and not MODEL_REGISTRY[MODEL_ID]["path"].is_dir(): |
| raise ValueError( |
| "Assistant checkpoint inference currently falls back to the full model, " |
| f"but the full model mount is missing at {MODEL_REGISTRY[MODEL_ID]['path']}." |
| ) |
| prompt = (prompt or "").strip() |
| if not prompt: |
| raise ValueError("Write a prompt before generating.") |
| if len(prompt) > 20_000: |
| raise ValueError("The prompt is too long; keep it below 20,000 characters.") |
| if len(system_prompt or "") > 20_000: |
| raise ValueError("The system instruction is too long; keep it below 20,000 characters.") |
| validate_controls( |
| max_new_tokens=max_new_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| top_k=top_k, |
| repetition_penalty=repetition_penalty, |
| reasoning_strength=reasoning_strength, |
| ) |
| if not bool(randomize_seed): |
| choose_seed(seed, False) |
| except (TypeError, ValueError) as error: |
| valid = False |
| message = str(error) |
|
|
| verdicts = [gr.validate(valid, message)] |
| verdicts.extend(gr.validate(True, "") for _ in range(15)) |
| return tuple(verdicts) |
|
|
|
|
| def _stop_conversation(chat_snapshot): |
| return list(chat_snapshot or []), "Stopped · the unfinished turn was not added to model history" |
|
|
|
|
| def _clear_conversation(): |
| return [], [], [], "", None, None, "Ready · native greedy · reasoning high" |
|
|
|
|
| def _set_preset(name: str): |
| return preset_values(name) |
|
|
|
|
| CSS = """ |
| :root { |
| --ink: #161225; |
| --muted: #686177; |
| --line: #e8e1f1; |
| --paper: #ffffff; |
| --wash: #faf8fd; |
| --violet: #6d28d9; |
| --cyan: #0e7490; |
| } |
| |
| .gradio-container { |
| max-width: 1180px !important; |
| margin: 0 auto !important; |
| background: |
| radial-gradient(circle at 8% 0%, rgba(109, 40, 217, .12), transparent 31rem), |
| radial-gradient(circle at 92% 0%, rgba(14, 116, 144, .10), transparent 29rem), |
| var(--wash); |
| } |
| |
| #hero { |
| padding: 26px 28px 22px; |
| border: 1px solid var(--line); |
| border-radius: 22px; |
| background: rgba(255, 255, 255, .90); |
| box-shadow: 0 18px 50px rgba(41, 24, 72, .07); |
| } |
| |
| #hero h1 { margin-bottom: 7px; letter-spacing: -.03em; } |
| #hero p { color: var(--muted); margin-bottom: 0; } |
| #chat { border: 1px solid var(--line); border-radius: 18px; background: var(--paper); } |
| #prompt textarea, .message-wrap, .prose, .md { unicode-bidi: plaintext; text-align: start; } |
| #prompt textarea { direction: auto; font-size: 1rem; } |
| #run-button { min-height: 52px; } |
| .status { color: var(--muted); min-height: 28px; } |
| .privacy-note { color: var(--muted); font-size: .88rem; } |
| |
| @media (max-width: 760px) { |
| #hero { padding: 19px; } |
| .gradio-container { padding: 9px !important; } |
| } |
| """ |
|
|
|
|
| THEME = gr.themes.Soft( |
| primary_hue="violet", |
| secondary_hue="cyan", |
| neutral_hue="slate", |
| ) |
|
|
|
|
| with gr.Blocks(title="Muse Glimmer 30B", analytics_enabled=False) as demo: |
| selected_model = gr.Dropdown( |
| choices=MODEL_CHOICES, |
| value=MODEL_DEFAULT_ID, |
| label="Model checkpoint", |
| info="Choose the full BF16 or compact assistant checkpoint for this turn.", |
| interactive=True, |
| allow_custom_value=True, |
| ) |
| model_history = gr.State([]) |
| committed_chat = gr.State([]) |
| selected_image = gr.State(None) |
|
|
| gr.Markdown( |
| """ |
| # Muse Glimmer · private inference |
| Text + image chat on either the official **full BF16** model or its **assistant checkpoint**. |
| Native greedy decoding is the default; Meta's sampling recipe is one click away. Reasoning is |
| parsed separately. |
| """, |
| elem_id="hero", |
| ) |
|
|
| chatbot = gr.Chatbot( |
| label="Conversation", |
| height=570, |
| layout="panel", |
| buttons=["copy", "copy_all"], |
| reasoning_tags=[("<think>", "</think>")], |
| placeholder="Ask a question or attach an image to begin.", |
| sanitize_html=True, |
| elem_id="chat", |
| ) |
| status = gr.Markdown( |
| "Ready · native greedy · reasoning high", |
| elem_classes="status", |
| ) |
|
|
| with gr.Row(equal_height=True): |
| prompt = gr.Textbox( |
| label="Prompt", |
| placeholder="Ask in English, עברית, العربية, or another supported language…", |
| lines=3, |
| max_lines=9, |
| max_length=20_000, |
| autofocus=True, |
| scale=4, |
| elem_id="prompt", |
| ) |
| image = gr.Image( |
| label="Optional image · this turn", |
| type="pil", |
| sources=["upload", "clipboard"], |
| height=180, |
| scale=2, |
| ) |
| IMAGE_CHANGE_API_NAME = "set_image" |
| PRESET_CHANGE_API_NAME = "set_generation_preset" |
| STOP_API_NAME = "stop_generation" |
| CLEAR_API_NAME = "clear_conversation" |
|
|
| image.change( |
| _coerce_image_input, |
| inputs=image, |
| outputs=selected_image, |
| queue=False, |
| api_name=IMAGE_CHANGE_API_NAME, |
| api_visibility="private", |
| ) |
|
|
| with gr.Row(): |
| run_button = gr.Button("Generate", variant="primary", elem_id="run-button") |
| stop_button = gr.Button("Stop", variant="stop") |
| clear_button = gr.Button("Clear") |
|
|
| with gr.Accordion("Generation controls", open=False): |
| preset = gr.Radio( |
| choices=list(PRESETS), |
| value=NATIVE_GREEDY, |
| label="Preset", |
| info="Native greedy matches generation_config.json. Meta sampling applies the model-card recipe.", |
| ) |
| with gr.Row(): |
| reasoning_strength = gr.Dropdown( |
| choices=["low", "medium", "high", "xhigh"], |
| value="high", |
| label="Reasoning strength", |
| ) |
| max_new_tokens = gr.Slider( |
| minimum=32, |
| maximum=MAX_NEW_TOKENS, |
| value=DEFAULT_MAX_NEW_TOKENS, |
| step=32, |
| label="Max new tokens", |
| info="App response budget; 512 is the default.", |
| ) |
| repetition_penalty = gr.Slider( |
| minimum=0.8, |
| maximum=1.3, |
| value=DEFAULT_REPETITION_PENALTY, |
| step=0.01, |
| label="Repetition penalty", |
| ) |
|
|
| do_sample = gr.Checkbox( |
| value=False, |
| label="Sampling", |
| info="Off is the checkpoint default. When off, temperature/top-p/top-k are ignored.", |
| ) |
| with gr.Row(): |
| temperature = gr.Slider( |
| minimum=0.05, |
| maximum=2.0, |
| value=DEFAULT_TEMPERATURE, |
| step=0.05, |
| label="Temperature", |
| ) |
| top_p = gr.Slider( |
| minimum=0.05, |
| maximum=1.0, |
| value=DEFAULT_TOP_P, |
| step=0.01, |
| label="Top-p", |
| ) |
| top_k = gr.Slider( |
| minimum=1, |
| maximum=200, |
| value=DEFAULT_TOP_K, |
| step=1, |
| label="Top-k", |
| ) |
|
|
| with gr.Row(): |
| seed = gr.Number( |
| value=DEFAULT_SEED, |
| precision=0, |
| minimum=0, |
| maximum=2_147_483_647, |
| label="Seed", |
| ) |
| randomize_seed = gr.Checkbox(value=False, label="Randomize seed each turn") |
| show_reasoning = gr.Checkbox(value=True, label="Show reasoning") |
|
|
| system_prompt = gr.Textbox( |
| value="", |
| label="Optional system instruction", |
| placeholder="Blank uses the model's built-in helpful-assistant system message.", |
| lines=3, |
| max_length=20_000, |
| ) |
|
|
| gr.Markdown( |
| f""" |
| **Private Space.** This app adds no prompt, reply, or image persistence and does not log |
| their contents. Inference runs on Hugging Face-hosted ZeroGPU `xlarge`; `xlarge` uses 2× |
| ZeroGPU quota. Model revisions: `{MODEL_REVISION}` and `{ASSISTANT_MODEL_REVISION}`. |
| No tools are connected or executed. |
| [Usage policy](https://huggingface.co/meta-models/Muse-Glimmer-30B/blob/{MODEL_REVISION}/USAGE_POLICY.md) |
| """, |
| elem_classes="privacy-note", |
| ) |
|
|
| preset.change( |
| _set_preset, |
| inputs=preset, |
| outputs=[do_sample, temperature, top_p, top_k], |
| queue=False, |
| api_name=PRESET_CHANGE_API_NAME, |
| api_visibility="private", |
| ) |
|
|
| generation_inputs = [ |
| prompt, |
| selected_image, |
| selected_model, |
| chatbot, |
| model_history, |
| system_prompt, |
| reasoning_strength, |
| do_sample, |
| max_new_tokens, |
| temperature, |
| top_p, |
| top_k, |
| repetition_penalty, |
| seed, |
| randomize_seed, |
| show_reasoning, |
| ] |
| generation_outputs = [chatbot, model_history, committed_chat, prompt, image, status] |
|
|
| generation_event = run_button.click( |
| fn=_generate_turn, |
| inputs=generation_inputs, |
| outputs=generation_outputs, |
| concurrency_limit=1, |
| concurrency_id="muse-glimmer-xlarge", |
| trigger_mode="once", |
| api_name="chat", |
| api_visibility="private", |
| api_description="Run a private Muse Glimmer text or image chat turn.", |
| show_progress="minimal", |
| validator=_validate_generation_request, |
| ) |
|
|
| submit_event = prompt.submit( |
| fn=_generate_turn, |
| inputs=generation_inputs, |
| outputs=generation_outputs, |
| concurrency_limit=1, |
| concurrency_id="muse-glimmer-xlarge", |
| trigger_mode="once", |
| api_name=SUBMIT_API_NAME, |
| api_visibility="private", |
| api_description="Submit a private Muse Glimmer text or image chat turn.", |
| show_progress="minimal", |
| queue=True, |
| validator=_validate_generation_request, |
| ) |
|
|
| stop_button.click( |
| _stop_conversation, |
| inputs=committed_chat, |
| outputs=[chatbot, status], |
| cancels=[generation_event, submit_event], |
| queue=False, |
| api_name=STOP_API_NAME, |
| api_visibility="private", |
| ) |
|
|
| clear_button.click( |
| _clear_conversation, |
| inputs=None, |
| outputs=[chatbot, model_history, committed_chat, prompt, image, selected_image, status], |
| cancels=[generation_event, submit_event], |
| queue=False, |
| api_name=CLEAR_API_NAME, |
| api_visibility="private", |
| ) |
|
|
|
|
| demo.queue(default_concurrency_limit=1, max_size=8) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch(theme=THEME, css=CSS) |
|
|