Instructions to use Audante/ACE-Step-1.5-mlx-8bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use Audante/ACE-Step-1.5-mlx-8bit with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download Audante/ACE-Step-1.5-mlx-8bit --local-dir ACE-Step-1.5-mlx-8bit
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download ace-step-mlx-quant.patch from Audante/ACE-Step-1.5-mlx-8bit: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/Audante/ACE-Step-1.5-mlx-8bit/resolve/main/ace-step-mlx-quant.patch
- Command line
-
hf download hf://Audante/ACE-Step-1.5-mlx-8bit/ace-step-mlx-quant.patch
-
curl -L -o ace-step-mlx-quant.patch https://huggingface.co/Audante/ACE-Step-1.5-mlx-8bit/resolve/main/ace-step-mlx-quant.patch
13.3 kB
| diff --git a/acestep/api/model_download.py b/acestep/api/model_download.py | |
| index 92257c9..00f3176 100644 | |
| --- a/acestep/api/model_download.py | |
| +++ b/acestep/api/model_download.py | |
| def ensure_model_downloaded(model_name: str, checkpoint_dir: str) -> str: | |
| print(f"[Model Download] Model {model_name} already exists at {model_path}") | |
| return model_path | |
| + if os.environ.get("ACESTEP_NO_DOWNLOAD") == "1": # Audante patch: models come only with Audante's checksums | |
| + raise RuntimeError(f"{model_name} is not installed ({model_path}); ACESTEP_NO_DOWNLOAD=1 forbids downloading it") | |
| + | |
| repo_id = MODEL_REPO_MAPPING.get(model_name, DEFAULT_REPO_ID) | |
| print(f"[Model Download] Model {model_name} not found, checking network...") | |
| diff --git a/acestep/api/startup_llm_init.py b/acestep/api/startup_llm_init.py | |
| index dd5199d..8759630 100644 | |
| --- a/acestep/api/startup_llm_init.py | |
| +++ b/acestep/api/startup_llm_init.py | |
| def initialize_llm_at_startup( | |
| ensure_model_downloaded(lm_model_name, checkpoint_dir) | |
| except Exception as exc: | |
| print(f"[API Server] Warning: Failed to download LLM model: {exc}") | |
| + if os.environ.get("ACESTEP_NO_DOWNLOAD") == "1": # Audante patch: say why there is no language model | |
| + app.state._llm_initialized = False | |
| + app.state._llm_init_error = f"{exc}" + ("" if is_supported else f" ({warning_msg})") | |
| + return | |
| llm_status, llm_ok = llm_handler.initialize( | |
| checkpoint_dir=checkpoint_dir, | |
| diff --git a/acestep/core/generation/handler/init_service_loader.py b/acestep/core/generation/handler/init_service_loader.py | |
| index 040c5ce..68eca1d 100644 | |
| --- a/acestep/core/generation/handler/init_service_loader.py | |
| +++ b/acestep/core/generation/handler/init_service_loader.py | |
| from acestep import gpu_config | |
| from .init_service_loader_components import InitServiceLoaderComponentsMixin | |
| +# Audante patch: a checkpoint may carry its DiT decoder as a pre-quantized MLX file (mlx_dit.safetensors with | |
| +# mlx_dit.json, next to a model.safetensors without decoder.* weights). Then the PyTorch decoder stays empty and | |
| +# mlx_dit_init.py fills the MLX decoder from that file instead of converting it from PyTorch. | |
| +def audante_mlx_dit_file(checkpoint_path, device): | |
| + """The checkpoint's pre-quantized MLX DiT, or None when it has none.""" | |
| + path = os.path.join(checkpoint_path, "mlx_dit.safetensors") | |
| + if not os.path.exists(path): | |
| + return None | |
| + from acestep.models.mlx import mlx_available | |
| + | |
| + if device not in ("mps", "cpu") or not mlx_available(): | |
| + raise RuntimeError(f"{path}: this checkpoint's DiT runs only in MLX (Apple Silicon), device here: {device}") | |
| + return path | |
| + | |
| + | |
| +class _AudanteSkipDecoderInit: | |
| + """While a checkpoint without decoder weights loads, transformers would initialise them at random (time and | |
| + memory for nothing): skip that, the decoder is emptied right after loading.""" | |
| + | |
| + def __init__(self, active): | |
| + self.active = bool(active) | |
| + | |
| + def __enter__(self): | |
| + if self.active: | |
| + from transformers import modeling_utils as mu | |
| + | |
| + self.orig = orig = mu.PreTrainedModel._initialize_missing_keys | |
| + mu.PreTrainedModel._initialize_missing_keys = lambda model, keys, quantized: orig( | |
| + model, [k for k in keys if not k.startswith("decoder.")], quantized) | |
| + return self | |
| + | |
| + def __exit__(self, *exc): | |
| + if self.active: | |
| + from transformers import modeling_utils as mu | |
| + | |
| + mu.PreTrainedModel._initialize_missing_keys = self.orig | |
| + return False | |
| + | |
| + | |
| +def audante_empty_decoder(model): | |
| + """Replace every tensor of the PyTorch DiT decoder by an empty one (frees its memory; MLX runs the DiT).""" | |
| + for param in model.decoder.parameters(): | |
| + param.data = torch.empty(0, dtype=param.dtype, device=param.device) | |
| + for name, buf in list(model.decoder.named_buffers()): | |
| + owner, _, attr = name.rpartition(".") | |
| + module = model.decoder.get_submodule(owner) if owner else model.decoder | |
| + setattr(module, attr, torch.empty(0, dtype=buf.dtype, device=buf.device)) | |
| + | |
| + | |
| class InitServiceLoaderMixin(InitServiceLoaderComponentsMixin): | |
| """Helpers for heavy model component loading.""" | |
| class InitServiceLoaderMixin(InitServiceLoaderComponentsMixin): | |
| last_attn_error = None | |
| self.model = None | |
| + self._audante_mlx_dit = audante_mlx_dit_file(model_checkpoint_path, device) # Audante patch | |
| for candidate in attn_candidates: | |
| try: | |
| logger.info(f"[initialize_service] Attempting to load model with attention implementation: {candidate}") | |
| - self.model = AutoModel.from_pretrained( | |
| - model_checkpoint_path, | |
| - trust_remote_code=True, | |
| - attn_implementation=candidate, | |
| - dtype=self.dtype, | |
| - ) | |
| + with _AudanteSkipDecoderInit(self._audante_mlx_dit): | |
| + self.model = AutoModel.from_pretrained( | |
| + model_checkpoint_path, | |
| + trust_remote_code=True, | |
| + attn_implementation=candidate, | |
| + dtype=self.dtype, | |
| + ) | |
| attn_implementation = candidate | |
| break | |
| except Exception as exc: | |
| class InitServiceLoaderMixin(InitServiceLoaderComponentsMixin): | |
| self.config = self.model.config | |
| self._sync_alignment_config() | |
| self._apply_cuda_bool_argsort_workaround() | |
| + if self._audante_mlx_dit: # Audante patch: the DiT comes from the MLX file | |
| + audante_empty_decoder(self.model) | |
| + logger.info("[initialize_service] Audante patch: PyTorch decoder left empty (pre-quantized MLX DiT)") | |
| if not self.offload_to_cpu: | |
| self.model = self.model.to(device).to(self.dtype) | |
| diff --git a/acestep/core/generation/handler/mlx_dit_init.py b/acestep/core/generation/handler/mlx_dit_init.py | |
| index 63cedde..ff7e77a 100644 | |
| --- a/acestep/core/generation/handler/mlx_dit_init.py | |
| +++ b/acestep/core/generation/handler/mlx_dit_init.py | |
| """MLX DiT initialization helpers for Apple Silicon acceleration.""" | |
| +import json | |
| +import os | |
| + | |
| from loguru import logger | |
| +def audante_load_mlx_dit(handler, mlx_decoder, convert_and_load): | |
| + """Audante patch: fill the MLX DiT from the checkpoint's pre-quantized file (every Linear of the decoder | |
| + group-wise affine at `bits`, the layers in `keep8` at 8 bits), else convert it from PyTorch as upstream does.""" | |
| + path = getattr(handler, "_audante_mlx_dit", None) | |
| + if not path: | |
| + convert_and_load(handler.model, mlx_decoder) | |
| + return | |
| + import mlx.core as mx | |
| + import mlx.nn as nn | |
| + | |
| + with open(os.path.join(os.path.dirname(path), "mlx_dit.json")) as f: | |
| + spec = json.load(f) | |
| + weights = mx.load(path) | |
| + group, bits, keep8 = spec["group_size"], spec["bits"], tuple(spec.get("keep8", ())) | |
| + nn.quantize(mlx_decoder, group, bits, class_predicate=lambda p, m: f"{p}.scales" in weights and { | |
| + "group_size": group, "bits": 8 if p.startswith(keep8) else bits}) | |
| + mlx_decoder.load_weights(list(weights.items()), strict=True) | |
| + mx.eval(mlx_decoder.parameters()) | |
| + logger.info(f"[MLX-DiT] Audante patch: pre-quantized {bits}-bit DiT from {path}") | |
| + | |
| + | |
| +def audante_free_torch_decoder(handler): | |
| + """Audante patch: with the original files and ACESTEP_FREE_TORCH_DIT=1, empty the PyTorch decoder once the MLX | |
| + DiT runs (6.3 GB in float32; only the Gradio UI's lyric score and LRC tools would call it). Same audio.""" | |
| + if getattr(handler, "_audante_mlx_dit", None) or os.environ.get("ACESTEP_FREE_TORCH_DIT") != "1": | |
| + return | |
| + import torch | |
| + from acestep.core.generation.handler.init_service_loader import audante_empty_decoder | |
| + | |
| + audante_empty_decoder(handler.model) | |
| + if torch.backends.mps.is_available(): | |
| + torch.mps.empty_cache() | |
| + logger.info("[MLX-DiT] Audante patch: PyTorch decoder freed") | |
| + | |
| + | |
| class MlxDitInitMixin: | |
| """Initialize native MLX DiT decoder state used by generation runtime.""" | |
| class MlxDitInitMixin: | |
| from acestep.models.mlx.dit_convert import convert_and_load | |
| mlx_decoder = MLXDiTDecoder.from_config(self.config) | |
| - convert_and_load(self.model, mlx_decoder) | |
| + audante_load_mlx_dit(self, mlx_decoder, convert_and_load) # Audante patch | |
| mlx_decoder.materialize_static_buffers() | |
| self.mlx_decoder = mlx_decoder | |
| self.use_mlx_dit = True | |
| self.mlx_dit_compiled = compile_model | |
| + audante_free_torch_decoder(self) # Audante patch | |
| logger.info( | |
| "[MLX-DiT] Native MLX DiT decoder initialized successfully " | |
| f"(mx.compile={compile_model})." | |
| ) | |
| return True | |
| except Exception as exc: # noqa: BLE001 | |
| + if getattr(self, "_audante_mlx_dit", None): # Audante patch: no PyTorch decoder to fall back to | |
| + raise | |
| logger.warning(f"[MLX-DiT] Failed to initialize MLX decoder (non-fatal): {exc}") | |
| self.mlx_decoder = None | |
| self.use_mlx_dit = False | |
| diff --git a/acestep/llm_inference.py b/acestep/llm_inference.py | |
| index 3690af5..9e3ae07 100644 | |
| --- a/acestep/llm_inference.py | |
| +++ b/acestep/llm_inference.py | |
| class LLMHandler: | |
| phase1_time = 0.0 | |
| phase2_time = 0.0 | |
| + # Audante patch: upstream seeds the MLX LM only in batch mode, so on a Mac a fixed seed gives other audio codes | |
| + # each time (MLX seeds from the clock). With ACESTEP_MLX_LM_SEED=1 a single song is seeded too. | |
| + self._audante_seed = (int(seeds[0]) if not is_batch and seeds and os.environ.get("ACESTEP_MLX_LM_SEED") == "1" | |
| + else None) | |
| + | |
| # Handle seeds for batch mode | |
| if is_batch: | |
| if seeds is None: | |
| class LLMHandler: | |
| """ | |
| try: | |
| import mlx.core as mx | |
| - from mlx_lm.utils import load as mlx_load | |
| + from pathlib import Path as _Path | |
| + from mlx_lm.utils import load_model as mlx_load_model | |
| logger.info(f"Loading MLX model from {model_path}") | |
| start_time = time.time() | |
| # Try standard mlx-lm load first | |
| + # Audante patch: load_model, not load: the model only (initialize() has loaded the tokenizer already, | |
| + # load would read it a second time) and never a download | |
| try: | |
| - self._mlx_model, _ = mlx_load(model_path) | |
| + self._mlx_model, _ = mlx_load_model(_Path(model_path)) | |
| except Exception as first_err: | |
| # The ACE-Step 5Hz LM checkpoints store safetensors keys without | |
| # the "model." prefix (e.g. "layers.0.xxx" instead of | |
| class LLMHandler: | |
| # Single mode | |
| formatted_prompt = formatted_prompt_list[0] | |
| + if getattr(self, "_audante_seed", None) is not None: # Audante patch: metadata with the seed, codes with seed + 1 | |
| + mx.random.seed(self._audante_seed + (1 if generation_phase == "codes" else 0)) | |
| + logger.info(f"Audante patch: MLX LM seeded ({generation_phase})") | |
| return self._run_mlx_single( | |
| formatted_prompt=formatted_prompt, | |
| temperature=temperature, | |
| diff --git a/acestep/model_downloader.py b/acestep/model_downloader.py | |
| index 30d9f9a..51778d5 100644 | |
| --- a/acestep/model_downloader.py | |
| +++ b/acestep/model_downloader.py | |
| def _smart_download( | |
| Returns: | |
| Tuple of (success, message) | |
| """ | |
| + if os.environ.get("ACESTEP_NO_DOWNLOAD") == "1": # Audante patch: models come only with Audante's checksums | |
| + error_msg = f"{repo_id} is not installed ({local_dir}), and ACESTEP_NO_DOWNLOAD=1 forbids downloading it" | |
| + logger.error(f"[Model Download] {error_msg}") | |
| + return False, error_msg | |
| + | |
| # Ensure directory exists | |
| local_dir.mkdir(parents=True, exist_ok=True) | |
| diff --git a/acestep/models/mlx/__init__.py b/acestep/models/mlx/__init__.py | |
| index 8135ab1..dae4006 100644 | |
| --- a/acestep/models/mlx/__init__.py | |
| +++ b/acestep/models/mlx/__init__.py | |
| def is_mlx_available() -> bool: | |
| # Verify we can actually create arrays (Metal backend works) | |
| _ = mx.array([1.0]) | |
| mx.eval(_) | |
| + _audante_cache_limit(mx) | |
| return True | |
| except Exception: | |
| return False | |
| +def _audante_cache_limit(mx) -> None: | |
| + """Audante patch: MLX keeps freed buffers for reuse and lets that cache grow towards the whole memory; cap it | |
| + at ACESTEP_MLX_CACHE_GB (same audio, a few GB less at the peak).""" | |
| + import os | |
| + | |
| + gb = os.environ.get("ACESTEP_MLX_CACHE_GB", "").strip() | |
| + if gb: | |
| + mx.set_cache_limit(int(float(gb) * 2**30)) | |
| + print(f"Audante patch: MLX cache limit {gb} GB", flush=True) | |
| + | |
| + | |
| _MLX_AVAILABLE = None | |