MIDI-GPT / midi_gpt_runtime.py
harp-dev's picture
Deploy MIDI-GPT HARP endpoint
9ae6560 verified
Raw
History Blame Contribute Delete
4.61 kB
from __future__ import annotations
import threading
from pathlib import Path
import torch
from midigpt import Score
from midigpt.inference import (
GenerationRequest,
InferenceConfig,
InferenceEngine,
TrackPrompt,
)
MODEL_REPOSITORY = "Metacreation/MIDI-GPT"
MODEL_NAME = "yellow_small"
MODEL_FILENAME = "yellow_small-final.safetensors"
_engine: InferenceEngine | None = None
_engine_lock = threading.Lock()
def _patch_searchsorted_device_mismatch() -> None:
if getattr(torch.searchsorted, "_midigpt_device_compat", False):
return
original = torch.searchsorted
def device_safe_searchsorted(sorted_sequence, values, *args, **kwargs):
if isinstance(values, torch.Tensor) and values.device != sorted_sequence.device:
values = values.to(sorted_sequence.device)
return original(sorted_sequence, values, *args, **kwargs)
device_safe_searchsorted._midigpt_device_compat = True
torch.searchsorted = device_safe_searchsorted
_patch_searchsorted_device_mismatch()
def _get_engine() -> InferenceEngine:
global _engine
if _engine is not None:
return _engine
with _engine_lock:
if _engine is None:
device = "cuda" if torch.cuda.is_available() else "cpu"
_engine = InferenceEngine.from_pretrained(
MODEL_REPOSITORY,
filename=MODEL_FILENAME,
device=device,
)
return _engine
def rewrite_bars(
input_path: Path,
output_path: Path,
track_index: int,
start_bar: int,
bar_count: int,
temperature: float,
top_p: float,
seed: int,
polyphony_limit: int,
) -> dict:
score = Score.from_midi(str(input_path))
track_count = len(score.tracks)
if track_count == 0:
raise ValueError("The MIDI file does not contain any tracks.")
if track_index < 0 or track_index >= track_count:
raise ValueError(
f"Track index {track_index} is out of range. "
f"This file has {track_count} tracks (0-{track_count - 1})."
)
bar_counts = [len(track.bars) for track in score.tracks]
if len(set(bar_counts)) != 1:
raise ValueError(
"MIDI-GPT requires every track to span the same number of bars."
)
total_bars = bar_counts[0]
if total_bars < 4:
raise ValueError(
f"MIDI-GPT requires at least 4 bars. This file has {total_bars}."
)
if start_bar < 0 or start_bar + bar_count > total_bars:
raise ValueError(
f"Bars {start_bar}-{start_bar + bar_count - 1} are outside "
f"the available range 0-{total_bars - 1}."
)
target_bars = list(range(start_bar, start_bar + bar_count))
original_notes = [
[
(
note.pitch,
note.velocity,
note.onset_ticks,
note.duration_ticks,
)
for note in score.tracks[track_index].bars[index].notes
]
for index in target_bars
]
prompts = []
for index in range(track_count):
prompts.append(
TrackPrompt(
id=index,
bars=target_bars if index == track_index else [],
)
)
model_dim = 4 if total_bars < 8 else 8
request = GenerationRequest(
tracks=prompts,
config=InferenceConfig(
temperature=temperature,
top_p=top_p,
seed=seed,
model_dim=model_dim,
mask_mode="attention",
bars_per_step=1,
polyphony_hard_limit=polyphony_limit,
novelty_check=False,
),
)
result = _get_engine().session(score, request).run()
output_path.parent.mkdir(parents=True, exist_ok=True)
result.to_midi(str(output_path))
generated_notes = sum(
len(result.tracks[track_index].bars[index].notes)
for index in target_bars
)
generated_content = [
[
(
note.pitch,
note.velocity,
note.onset_ticks,
note.duration_ticks,
)
for note in result.tracks[track_index].bars[index].notes
]
for index in target_bars
]
return {
"model": MODEL_NAME,
"track_count": track_count,
"total_bars": total_bars,
"rewritten_track": track_index,
"rewritten_bars": target_bars,
"generated_notes": generated_notes,
"content_changed": generated_content != original_notes,
"seed": seed,
}