From adebf6387731f2b6a20bee5793e6f10fc8ef20c4 Mon Sep 17 00:00:00 2001 From: Concedo <39025047+LostRuins@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:53:02 +0800 Subject: [PATCH] ace converter --- Makefile | 3 + otherarch/acestep/acestep_convert.py | 287 +++++++++++++++++++++++++++ 2 files changed, 290 insertions(+) create mode 100644 otherarch/acestep/acestep_convert.py diff --git a/Makefile b/Makefile index f2eccf159..3ea440f7a 100644 --- a/Makefile +++ b/Makefile @@ -915,6 +915,9 @@ quantize_mpt: otherarch/tools/mpt_quantize.cpp otherarch/tools/common-ggml.cpp g $(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) quantize_clip: tools/mtmd/clip.cpp tools/quantclip.cpp ggml_v3.o ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS) $(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) +quantize_ace: otherarch/acestep/quantize-acestep.cpp tools/mtmd/clip.cpp ggml_v3.o ggml.o ggml-cpu.o ggml-ops.o ggml-vec.o ggml-binops.o ggml-unops.o llama.o ggml-backend_default.o ggml-backend-reg_default.o ggml-repack.o $(OBJS_FULL) $(OBJS) + $(CXX) $(CXXFLAGS) $^ -o $@ $(LDFLAGS) + #window simple clinfo simplecpuinfo: simplecpuinfo.cpp diff --git a/otherarch/acestep/acestep_convert.py b/otherarch/acestep/acestep_convert.py new file mode 100644 index 000000000..7d14ec981 --- /dev/null +++ b/otherarch/acestep/acestep_convert.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# convert.py: safetensors to GGUF for ACE-Step (LM, DiT, TextEncoder, VAE) +# Reads from checkpoints/, writes GGUF to models/ +# Each GGUF is self-contained: weights + config + tokenizer + silence_latent + +import os +import sys +import json +import struct +import zipfile +import numpy as np +import gguf + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +CHECKPOINT_DIR = os.path.join(SCRIPT_DIR, "checkpoints") +OUTPUT_DIR = os.path.join(SCRIPT_DIR, "models") + +BF16 = gguf.GGMLQuantizationType.BF16 + +def log(tag, msg): + print("[%s] %s" % (tag, msg), file=sys.stderr, flush=True) + +# Safetensors reader +def read_sf_header(path): + with open(path, "rb") as f: + n = struct.unpack(" GGUF KV) +def add_bpe_tokenizer(w, model_dir, tag): + vocab_path = os.path.join(model_dir, "vocab.json") + merges_path = os.path.join(model_dir, "merges.txt") + if not os.path.exists(vocab_path) or not os.path.exists(merges_path): + return False + + vocab = json.load(open(vocab_path, "r", encoding="utf-8")) + tokens = [""] * len(vocab) + for tok_str, tok_id in vocab.items(): + if 0 <= tok_id < len(tokens): + tokens[tok_id] = tok_str + + with open(merges_path, "r", encoding="utf-8") as f: + merges = [] + for line in f: + line = line.rstrip("\n\r") + if not line: + continue + if line.startswith("#version:"): + continue + merges.append(line) + + w.add_tokenizer_model("gpt2") + w.add_token_list(tokens) + w.add_token_merges(merges) + + log(tag, " tokenizer: %d vocab, %d merges" % (len(tokens), len(merges))) + return True + +# Main conversion +def convert_model(name, model_dir, output_path, model_type): + tag = "GGUF" + cfg_path = os.path.join(model_dir, "config.json") + if not os.path.exists(cfg_path): + log(tag, "skip %s: no config.json" % name) + return False + + cfg = json.load(open(cfg_path, "r", encoding="utf-8")) + sf_files = find_sf_files(model_dir) + if not sf_files: + log(tag, "skip %s: no safetensors" % name) + return False + + arch = ARCHS[model_type] + log(tag, "%s (%s, %d shard%s) -> %s" % ( + name, arch, len(sf_files), "" if len(sf_files) == 1 else "s", + os.path.basename(output_path))) + + w = gguf.GGUFWriter(output_path, arch, use_temp_file=True) + w.add_name(name) + add_metadata(w, cfg, model_type) + + # BPE tokenizer for LM and text encoder + if model_type in ("lm", "text-enc"): + add_bpe_tokenizer(w, model_dir, tag) + + # Model weights + n_tensors = 0 + n_bytes = 0 + for sf in sf_files: + c, b = add_tensors_from_sf(w, sf, tag) + n_tensors += c + n_bytes += b + if len(sf_files) > 1: + log(tag, " %s: %d tensors" % (os.path.basename(sf), c)) + + # silence_latent for DiT (read .pt, transpose, embed as f32 tensor) + if model_type == "dit": + sl = read_silence_latent(model_dir) + if sl is not None: + w.add_tensor("silence_latent", sl) + n_tensors += 1 + n_bytes += sl.nbytes + log(tag, " silence_latent: [%d, %d] f32 (%.1f MB)" % ( + sl.shape[0], sl.shape[1], sl.nbytes / (1 << 20))) + else: + log(tag, " WARNING: no silence_latent.pt found") + + log(tag, " total: %d tensors, %.1f GB" % (n_tensors, n_bytes / (1 << 30))) + + w.write_header_to_file() + w.write_kv_data_to_file() + w.write_tensors_to_file(progress=True) + w.close() + + out_mb = os.path.getsize(output_path) / (1 << 20) + log(tag, " wrote %.0f MB -> %s" % (out_mb, output_path)) + return True + +def main(): + if not os.path.isdir(CHECKPOINT_DIR): + log("GGUF", "checkpoints/ not found, run checkpoints.sh first") + sys.exit(1) + + os.makedirs(OUTPUT_DIR, exist_ok=True) + + entries = sorted(os.listdir(CHECKPOINT_DIR)) + converted = 0 + skipped = [] + + for name in entries: + model_dir = os.path.join(CHECKPOINT_DIR, name) + if not os.path.isdir(model_dir): + continue + + model_type = classify(name) + if model_type is None: + skipped.append(name) + continue + + output_path = os.path.join(OUTPUT_DIR, "%s-BF16.gguf" % name) + if os.path.exists(output_path): + log("GGUF", "skip %s: %s exists" % (name, os.path.basename(output_path))) + converted += 1 + continue + + if convert_model(name, model_dir, output_path, model_type): + converted += 1 + + if skipped: + log("GGUF", "skipped (unknown): %s" % ", ".join(skipped)) + log("GGUF", "done: %d model(s) in %s" % (converted, OUTPUT_DIR)) + +if __name__ == "__main__": + main()