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
File size: 13,330 Bytes
7a59458 | 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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | 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
@@ -101,6 +101,9 @@ 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
@@ -81,6 +81,10 @@ 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
@@ -11,6 +11,55 @@ 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."""
@@ -169,15 +218,17 @@ 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:
@@ -193,6 +244,9 @@ 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
@@ -1,8 +1,46 @@
"""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."""
@@ -26,17 +64,20 @@ 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
@@ -1352,6 +1352,11 @@ 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:
@@ -2942,14 +2947,17 @@ 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
@@ -4069,6 +4077,9 @@ 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
@@ -232,6 +232,11 @@ 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
@@ -17,11 +17,23 @@ 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
|