Getting Started¶
Installation¶
Inference only¶
Pre-built wheels for CPython 3.10–3.14 on Linux (x86_64), macOS (x86_64 + arm64), and Windows (AMD64). No compiler needed.
On Intel Macs, midigpt[inference] needs Python 3.12 or older: PyTorch stopped publishing macOS x86_64 wheels after 2.2, so it installs with torch<2.3 and numpy<2.
All extras¶
| Extra | What it adds |
|---|---|
inference |
torch>=2.0, tqdm, huggingface_hub, safetensors |
train |
PyTorch Lightning, HuggingFace datasets, pyarrow, python-dotenv, wandb |
realtime |
python-osc, Flask, Flask-SocketIO |
http |
FastAPI, uvicorn |
dev |
pytest, ruff, mypy |
docs |
mkdocs-material, mkdocstrings |
all |
realtime + train |
Developer install¶
git clone https://github.com/Metacreation-Lab/MIDI-GPT.git
cd MIDI-GPT
pip install -e ".[inference,dev]"
Prerequisites: Python 3.10+, CMake 3.21+, a C++20 compiler.
Loading a model¶
Load a pretrained model by name — downloaded from HuggingFace Hub and cached locally on first use:
from midigpt.inference import InferenceEngine
engine = InferenceEngine.from_pretrained("yellow_medium") # or "prism_medium", "expressive_medium"
When the model repo is gated, accept its license on Hugging Face and run hf auth login (or set HF_TOKEN) before the first download; see access tokens.
Load from a local checkpoint file:
See Models for a description of each checkpoint.
Working with scores¶
midigpt represents music as a Score — a list of Tracks, each containing a list of Bars with Notes.
Build from scratch¶
from midigpt import Score, Track, Bar, Note
score = Score(
tracks=[
Track(
bars=[Bar() for _ in range(8)],
instrument=0, # General MIDI program number
track_type="melodic",
)
],
resolution=480, # ticks per quarter note
tempo=500000, # microseconds per quarter note (= 120 BPM)
)
Load from MIDI¶
Save to MIDI¶
Infill¶
Fill in specific bars while keeping the rest of the arrangement intact:
from midigpt.inference import GenerationRequest, InferenceConfig, TrackPrompt
score = Score.from_midi("my_song.mid")
request = GenerationRequest(
tracks=[
TrackPrompt(id=0, bars=[4, 5, 6, 7]), # regenerate bars 4–7 on track 0
TrackPrompt(id=1, bars=[], ignore=True), # leave track 1 unchanged
],
config=InferenceConfig(
temperature=1.0,
top_p=0.95,
model_dim=8, # context window in bars — must be in the model's num_bars_map
mask_mode="attention", # the default, "auto", is "attention" on every released model
),
)
result = engine.session(score, request).run()
result.to_midi("output.mid")
Autoregressive generation¶
Generate a new track from scratch, conditioned on musical attributes:
score = Score(tracks=[Track(bars=[Bar() for _ in range(8)])]) # one empty 8-bar track
request = GenerationRequest(
tracks=[
TrackPrompt(
id=0,
bars=list(range(8)), # the bars to generate, in order
autoregressive=True,
attributes={"max_polyphony": 3}, # quantized attribute level
controls={"time_signature": 0}, # index into encoder TS list
),
],
config=InferenceConfig(
temperature=1.0,
model_dim=8,
mask_mode="attention",
polyphony_hard_limit=4,
),
)
result = engine.session(score, request).run()
Attribute control¶
Introspect available attribute controls for the loaded model:
engine._analyzer.attribute_sizes()
# {"note_density": 10, "min_polyphony": 10, "max_polyphony": 10, "min_note_duration": 6, ...}
engine._analyzer.attribute_value_labels()
# {"note_density": ["very sparse", "sparse", ..., "very dense"], ...}
engine._analyzer.attribute_track_types()
# {"note_density": "drum", "min_polyphony": "melodic", ...}
engine._analyzer.attribute_levels()
# {"note_density": "track", ...} — "track" goes in attributes, "bar" in bar_attributes
Pass quantized levels (integers in [0, size)) in TrackPrompt.attributes. Level 0 is the sparsest / quietest bin; size - 1 is the densest / loudest. An attribute set on the wrong kind of track (see attribute_track_types()) is rejected: on yellow, note_density is drum-only, polyphony and note duration melodic-only.
Per-bar overrides shape an attribute across bars. They take bar-level attributes, which prism and expressive have (yellow's are all track-level):
# prism_medium, melodic track
TrackPrompt(
id=0,
bars=[0, 1, 2, 3],
attributes={"max_note_duration": 3}, # track-level
bar_attributes={
0: {"bar_max_polyphony": 1}, # bar 0: one note at a time
3: {"bar_max_polyphony": 6}, # bar 3: chords
},
)
Sampling filters¶
InferenceConfig exposes a four-stage logit-filtering pipeline (top_k → top_p → mask_k → mask_p):
| Field | Default | Meaning |
|---|---|---|
top_k |
0 (off) |
Keep top-k highest-probability tokens |
top_p |
1.0 (off) |
Nucleus: keep the smallest set summing to ≥ top_p |
mask_k |
0 (off) |
Remove the top-k most-likely tokens (novelty pressure) |
mask_p |
0.0 (off) |
Anti-nucleus: remove tokens summing to ≥ mask_p from the top |
A small mask_k=1 or mask_p=0.3 pushes the model off its most-confident picks — useful when novelty_check=True is enabled and you want diverse retries.
Mask modes¶
Control how bars-to-be-generated appear in the context window:
| Mode | Behaviour |
|---|---|
"attention" |
Future bars zeroed in the KV cache via exact span masking |
"attention_approx" |
Single prefill mask + KV surgery; faster than "attention" |
"attention_skip" |
Future tokens filtered from input; position_ids passed explicitly |
"remove" |
Future bars omitted entirely from the token stream |
"auto" (default) |
"token" when the checkpoint has a MaskBar token, else "attention" |
"token" |
Encoder emits a MaskBar token (requires vocab support) |
Set via InferenceConfig(mask_mode="attention"); the default, "auto", picks "token" when the checkpoint supports it and "attention" otherwise. The "attention" family works on all currently-released models; "token" requires the encoder vocab to include a MaskBar domain — no released checkpoint has this yet.