Text Generation
Transformers
Safetensors
llama
slmkit
from-scratch
abc-music
music
abc-notation
folk-music
symbolic-music
text-generation-inference
Instructions to use m87techlabs/abc-folk with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use m87techlabs/abc-folk with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="m87techlabs/abc-folk")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("m87techlabs/abc-folk") model = AutoModelForCausalLM.from_pretrained("m87techlabs/abc-folk", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use m87techlabs/abc-folk with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "m87techlabs/abc-folk" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "m87techlabs/abc-folk", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/m87techlabs/abc-folk
- SGLang
How to use m87techlabs/abc-folk with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "m87techlabs/abc-folk" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "m87techlabs/abc-folk", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "m87techlabs/abc-folk" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "m87techlabs/abc-folk", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use m87techlabs/abc-folk with Docker Model Runner:
docker model run hf.co/m87techlabs/abc-folk
Download ui/viewer.js from m87techlabs/abc-folk: direct link, hf CLI and curl.
- Browser
- Download file 15.7 kB
-
https://huggingface.co/m87techlabs/abc-folk/resolve/main/ui/viewer.js
- Command line
-
hf download hf://m87techlabs/abc-folk/ui/viewer.js
-
curl -L -o viewer.js https://huggingface.co/m87techlabs/abc-folk/resolve/main/ui/viewer.js
15.7 kB
| // abc_music's viewer for `slm serve`'s playground (ADR 0008). `slm export` copies this directory | |
| // into the model as ui/; the page imports it and calls setup() and render(). | |
| // | |
| // It draws a generated tune as sheet music and plays it in the browser with abcjs | |
| // (https://www.abcjs.net/, MIT licence), loaded from jsDelivr with a pinned version and a | |
| // subresource-integrity hash: the browser refuses the file if the CDN serves anything else. | |
| // Playback samples come from abcjs's default soundfont (also fetched by the browser). | |
| const ABCJS = { | |
| version: "6.7.1", | |
| script: "https://cdn.jsdelivr.net/npm/abcjs@6.7.1/dist/abcjs-basic-min.js", | |
| scriptHash: "sha384-gO9mym1Z3WJwxNm4ZpC6ZQbMyiu+72akLTHzztpwTs6KYVd3NnfkQigzPk+Oqzqy", | |
| css: "https://cdn.jsdelivr.net/npm/abcjs@6.7.1/abcjs-audio.css", | |
| cssHash: "sha384-eK1u60r2vqfWom2CLxO79qAvQKkjn5j1PjFslU5/+6gScaYuFJ92tkZ1N4z0hXbF", | |
| }; | |
| // The forms the training corpus actually has most of (experiments.md Β§5: ask for what exists). | |
| // meter and L: (unit note length) are the most common pairing for each rhythm. | |
| const RHYTHMS = [ | |
| { rhythm: "reel", meter: "2/2", unit: "1/8" }, | |
| { rhythm: "jig", meter: "6/8", unit: "1/8" }, | |
| { rhythm: "hornpipe", meter: "2/4", unit: "1/16" }, | |
| { rhythm: "slip jig", meter: "9/8", unit: "1/8" }, | |
| { rhythm: "strathspey", meter: "4/4", unit: "1/8" }, | |
| { rhythm: null, meter: "3/4", unit: "1/8", label: "a tune in 3/4 (e.g. an air)" }, | |
| { rhythm: null, meter: "6/8", unit: "1/8", label: "a tune in 6/8" }, | |
| { rhythm: null, meter: "4/4", unit: "1/8", label: "a tune in 4/4" }, | |
| ]; | |
| const KEYS = ["G", "D", "A", "C", "F", "Bb", "Em", "Am", "Dm", "Bm", "Gm", "F#m", "Amix", "Dmix", "Ador", "Edor"]; | |
| const MODES = { "": "major", m: "minor", mix: "mixolydian", dor: "dorian" }; | |
| const INSTRUMENTS = [["fiddle", 40], ["piano", 0], ["flute", 73], ["accordion", 21], ["harp", 46]]; | |
| let loading = null; | |
| let playing = null; // the current SynthController, so a new result can stop the old one | |
| function loadAbcjs() { | |
| if (window.ABCJS) return Promise.resolve(); | |
| loading ??= new Promise((resolve, reject) => { | |
| const css = Object.assign(document.createElement("link"), { rel: "stylesheet", href: ABCJS.css, | |
| integrity: ABCJS.cssHash, crossOrigin: "anonymous" }); | |
| const js = Object.assign(document.createElement("script"), { src: ABCJS.script, | |
| integrity: ABCJS.scriptHash, crossOrigin: "anonymous" }); | |
| js.onload = resolve; | |
| js.onerror = () => reject(new Error(`could not load abcjs ${ABCJS.version} from jsDelivr (offline?)`)); | |
| const style = document.createElement("style"); | |
| style.textContent = ` | |
| .abc-paper { background: #fff; color: #000; border-radius: 6px; padding: 8px; margin-top: 10px; } | |
| .abc-paper .abcjs-highlight { fill: #2a78d6; } | |
| .abc-tools { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 4px 0 8px; } | |
| .abc-tools label { margin: 0; display: flex; gap: 6px; align-items: center; } | |
| .abc-tools select { font: inherit; padding: 4px 6px; border-radius: 6px; } | |
| .abc-tools a { font-size: 13px; } | |
| .abc-warnings { font-size: 12px; margin-top: 8px; } | |
| .abc-builder { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; } | |
| .abc-read { margin-top: 14px; border-top: 1px solid var(--line, #ddd); padding-top: 10px; font-size: 14px; } | |
| .abc-read h3 { margin: 0 0 8px; font-size: 15px; } | |
| .abc-read table { border-collapse: collapse; margin-bottom: 8px; } | |
| .abc-read th, .abc-read td { text-align: left; padding: 3px 12px 3px 0; } | |
| .abc-read th { color: var(--muted, #888); font-weight: 600; } | |
| .abc-read ul { margin: 4px 0; padding-left: 20px; } | |
| .abc-read details { margin-top: 6px; }`; | |
| document.head.append(css, style, js); | |
| }); | |
| return loading; | |
| } | |
| function keyWords(key) { | |
| const m = key.match(/^([A-G][b#]?)(m|mix|dor)?$/); | |
| return `${m[1]} ${MODES[m[2] ?? ""]}`; | |
| } | |
| // The request, in the format the model was trained on. For a fine-tuned model these are the same | |
| // templates as the project's SFT eval prompts (project.py: EVAL_WITH_RHYTHM, EVAL_WITHOUT_RHYTHM). | |
| function buildPrompt(stage, form, key) { | |
| if (stage === "sft") { | |
| const request = form.rhythm | |
| ? `${/^[aeiou]/.test(form.rhythm) ? "an" : "a"} ${form.rhythm} in ${keyWords(key)}, ${form.meter} time` | |
| : `a tune in ${form.meter} time in ${keyWords(key)}`; | |
| return `% ${request}\n`; | |
| } | |
| return (form.rhythm ? `R:${form.rhythm}\n` : "") + `M:${form.meter}\nL:${form.unit}\nK:${key}\n`; | |
| } | |
| function h(tag, attrs, ...children) { | |
| const el = Object.assign(document.createElement(tag), attrs); | |
| for (const c of children.flat()) if (c != null) el.append(c instanceof Node ? c : document.createTextNode(String(c))); | |
| return el; | |
| } | |
| function select(label, options) { | |
| const wrap = document.createElement("div"); | |
| const l = Object.assign(document.createElement("label"), { textContent: label }); | |
| const s = document.createElement("select"); | |
| for (const [text, value] of options) s.append(new Option(text, value)); | |
| l.append(s); | |
| wrap.append(l); | |
| return [wrap, s]; | |
| } | |
| export async function setup({ info, container, setPrompt }) { | |
| loadAbcjs().catch(() => {}); // start fetching early; render() reports failures | |
| const [formBox, formSel] = select("Ask for", RHYTHMS.map((f, i) => [f.label ?? `a ${f.rhythm} (${f.meter})`, i])); | |
| const [keyBox, keySel] = select("in the key of", KEYS.map((k) => [keyWords(k), k])); | |
| const grid = Object.assign(document.createElement("div"), { className: "abc-builder" }); | |
| grid.append(formBox, keyBox); | |
| const note = Object.assign(document.createElement("div"), { | |
| className: "hint", | |
| textContent: info.stage === "sft" | |
| ? "Builds a request in words, the way this model was fine-tuned. Or edit the prompt freely." | |
| : "This is a base model: the request is written as ABC headers. Or edit the prompt freely.", | |
| }); | |
| keySel.value = "D"; // matches the model card's example prompt (a reel in D), which the page shows first | |
| const update = () => setPrompt(buildPrompt(info.stage, RHYTHMS[Number(formSel.value)], keySel.value)); | |
| formSel.addEventListener("change", update); | |
| keySel.addEventListener("change", update); | |
| container.append(grid, note); | |
| } | |
| // ---------------------------------------------------------------- reading a tune, in plain words | |
| // For someone who doesn't read music: what was asked, what was written, and what the headers mean. | |
| // The precise checks are the project's graders (graders.py); this is an explanation, not a grade. | |
| const RHYTHM_NOTES = { | |
| reel: "a fast dance tune, two strong beats in every bar", | |
| jig: "a lively dance with a lilting one-two-three, one-two-three feel", | |
| "slip jig": "like a jig, but three groups of three in every bar", | |
| hornpipe: "a steady dance tune, usually played with a swing", | |
| strathspey: "a slow Scottish dance with short, sharp 'snapped' rhythms", | |
| polka: "a quick two-beat dance", waltz: "a three-beat dance", march: "a tune to walk in step to", | |
| air: "a slow song melody, not a dance", clog: "a dance tune for clog dancing, close to a hornpipe", | |
| }; | |
| const NOTE_LENGTHS = { 1: "whole", 2: "half", 4: "quarter", 8: "eighth", 16: "sixteenth" }; | |
| const MODE_NOTES = { major: "bright, the usual 'happy' scale", minor: "darker, often sad-sounding", | |
| mixolydian: "major with a flattened 7th, common in Irish tunes", dorian: "minor with a raised 6th, very common in Irish tunes" }; | |
| function header(text, letter) { return text.match(new RegExp(`^${letter}:\\s*(.+)$`, "m"))?.[1]?.trim() ?? null; } | |
| function normMeter(m) { return m === "C" ? "4/4" : m === "C|" ? "2/2" : m; } | |
| function meterWords(m) { | |
| const [n, d] = normMeter(m).split("/").map(Number); | |
| if (!n || !d) return m; | |
| const len = NOTE_LENGTHS[d] ?? `1/${d}`; | |
| return d === 8 && n % 3 === 0 && n > 3 | |
| ? `each bar holds ${n} ${len} notes, felt as ${n / 3} groups of three` | |
| : `each bar holds ${n} ${len} notes (${n} beats)`; | |
| } | |
| function keyName(k) { | |
| const m = (k ?? "").match(/^([A-G][b#]?)\s*(m(?:in)?|maj|mix|dor|lyd|phr|loc)?/i); | |
| if (!m) return null; | |
| const mode = { m: "minor", min: "minor", maj: "major", mix: "mixolydian", dor: "dorian", | |
| lyd: "lydian", phr: "phrygian", loc: "locrian" }[(m[2] ?? "").toLowerCase()] ?? "major"; | |
| return { tonic: m[1], mode, words: `${m[1]} ${mode}` }; | |
| } | |
| // The tune's notes after the headers: chord names, decorations, grace notes and comments removed. | |
| function body(text) { | |
| return text.split("\n").filter((l) => !/^[A-Za-z]:/.test(l) && !l.startsWith("%")).join("\n") | |
| .replace(/"[^"]*"|![^!]*!|\+[^+]*\+|\{[^}]*\}|\[[A-Za-z]:[^\]]*\]/g, " "); | |
| } | |
| function lastNote(b) { | |
| const notes = [...b.matchAll(/([_=^]*)([A-Ga-g])[,']*/g)]; | |
| return notes.length ? notes[notes.length - 1][2].toUpperCase() : null; | |
| } | |
| function barCount(b) { | |
| return b.split(/\|+|:\||\|:|::|\[\d/).filter((seg) => /[A-Ga-g]/.test(seg)).length; | |
| } | |
| function asked(prompt) { | |
| const line = prompt.split("\n")[0]; | |
| if (line.startsWith("%")) { | |
| const words = line.replace(/^%\s*/, ""); | |
| const tune = words.match(/^a tune in (\d+\/\d+) time in (.+)$/); | |
| if (tune) return { rhythm: null, meter: tune[1], key: tune[2] }; | |
| const m = words.match(/^(?:an? |compose an? |write an? )?(.+?) in (?:the key of )?([A-G][b#]? \w+)(?:, (\d+\/\d+) time)?$/); | |
| return m ? { rhythm: m[1], key: m[2], meter: m[3] ?? null } : null; | |
| } | |
| const k = keyName(header(prompt, "K")); | |
| return { rhythm: header(prompt, "R"), meter: header(prompt, "M"), key: k?.words ?? null }; | |
| } | |
| function explain(result) { | |
| const t = result.text; | |
| const wrote = { rhythm: header(t, "R"), meter: header(t, "M"), key: keyName(header(t, "K")) }; | |
| const want = asked(t.slice(0, t.length - result.completion.length)); | |
| const row = (label, a, w, same) => h("tr", {}, h("th", {}, label), h("td", {}, a ?? "β"), h("td", {}, w ?? "β"), | |
| h("td", {}, a == null ? "" : same ? "β" : "β")); | |
| const rows = want ? [ | |
| row("rhythm", want.rhythm, wrote.rhythm, want.rhythm?.toLowerCase() === wrote.rhythm?.toLowerCase()), | |
| row("time signature", want.meter, wrote.meter, want.meter && wrote.meter && normMeter(want.meter) === normMeter(wrote.meter)), | |
| row("key", want.key, wrote.key?.words, want.key?.toLowerCase() === wrote.key?.words.toLowerCase()), | |
| ] : []; | |
| const b = body(t); | |
| const end = lastNote(b); | |
| const bars = barCount(b); | |
| const parts = (b.match(/:\|/g) ?? []).length; | |
| const facts = [ | |
| wrote.rhythm && `R:${wrote.rhythm} β ${RHYTHM_NOTES[wrote.rhythm.toLowerCase()] ?? "the type of tune"}.`, | |
| wrote.meter && `M:${wrote.meter} β the time signature: ${meterWords(wrote.meter)}.`, | |
| header(t, "L") && `L:${header(t, "L")} β a plain letter lasts a 1/${header(t, "L").split("/")[1]} note; "A2" lasts twice as long, "A/" half as long.`, | |
| wrote.key && `K:${header(t, "K")} β the key, ${wrote.key.words}: ${MODE_NOTES[wrote.key.mode] ?? "a scale"}. Its home note is ${wrote.key.tonic}.`, | |
| `${bars} bars written out${parts ? `, with repeat signs (|: β¦ :|) marking parts that are played twice` : ""}. ` | |
| + "A typical folk tune has two 8-bar parts, each played twice.", | |
| end && wrote.key && (end === wrote.key.tonic[0] | |
| ? `It ends on ${end}, the home note β β about 80% of real tunes do.` | |
| : `It ends on ${end}, not the home note ${wrote.key.tonic} β β real tunes end at home about 80% of the time; this model, about a third.`), | |
| ].filter(Boolean); | |
| return h("section", { class: "abc-read" }, h("h3", {}, "Reading this tune"), | |
| rows.length ? h("table", {}, h("tr", {}, h("th", {}, ""), h("th", {}, "you asked for"), h("th", {}, "it wrote"), h("th", {}, "")), rows) : null, | |
| h("ul", {}, facts.map((f) => h("li", {}, f))), | |
| h("details", {}, h("summary", {}, "How to read ABC notation"), h("ul", {}, | |
| h("li", {}, "The lines with a letter and a colon are headers: R rhythm, M time signature, L note length, K key."), | |
| h("li", {}, "After them, every letter is a note: C D E F G A B, the white keys of a piano. Lowercase (c d eβ¦) is an octave higher; an apostrophe (c') higher still, a comma (C,) lower."), | |
| h("li", {}, "A number after a note makes it longer (A2 = two units), a slash shorter (A/ = half). ^ before a note is a sharp, _ a flat."), | |
| h("li", {}, "| marks the end of a bar; |: β¦ :| means \"play this part twice\". Letters in quotes are chord names for an accompanist.")))); | |
| } | |
| // A title for the sheet music: the request itself for a fine-tuned model ("% a jig in G ..."), | |
| // or what the headers asked for, for a base model. | |
| function titleFor(text) { | |
| const first = text.split("\n")[0]; | |
| if (first.startsWith("%")) return first.replace(/^%\s*/, "").slice(0, 60); | |
| const rhythm = text.match(/^R:\s*(.+)$/m)?.[1]?.trim(); | |
| const key = text.match(/^K:\s*(\S+)/m)?.[1]; | |
| return `${rhythm ?? "a tune"}${key ? ` in ${key}` : ""}`; | |
| } | |
| export async function render({ result, container }) { | |
| await loadAbcjs(); | |
| const ABC = window.ABCJS; | |
| if (playing) { playing.pause(); playing = null; } | |
| const title = titleFor(result.text); | |
| const abc = `X:1\nT:${title} (seed ${result.seed})\n${result.text}${result.text.endsWith("\n") ? "" : "\n"}`; | |
| const tools = Object.assign(document.createElement("div"), { className: "abc-tools" }); | |
| const audio = document.createElement("div"); | |
| const paper = Object.assign(document.createElement("div"), { className: "abc-paper" }); | |
| const warnings = Object.assign(document.createElement("div"), { className: "abc-warnings" }); | |
| container.append(tools, audio, paper, warnings, explain(result)); | |
| const tune = ABC.renderAbc(paper, abc, { responsive: "resize", add_classes: true })[0]; | |
| // What abcjs complained about while reading the tune: the browser's view of `plays`. | |
| const found = (tune.warnings ?? []).map((w) => w.replace(/<[^>]*>/g, "")); | |
| warnings.append(Object.assign(document.createElement("div"), { | |
| className: found.length ? "error" : "hint", | |
| textContent: found.length | |
| ? `abcjs found ${found.length} problem(s) reading this tune:\n` + found.slice(0, 5).join("\n") | |
| : "abcjs read the tune without complaint.", | |
| })); | |
| const [instBox, instSel] = select("Instrument", INSTRUMENTS); | |
| const fileName = `${title.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}-seed${result.seed}`; | |
| const abcLink = Object.assign(document.createElement("a"), { textContent: "Download .abc", download: `${fileName}.abc`, | |
| href: URL.createObjectURL(new Blob([abc], { type: "text/vnd.abc" })) }); | |
| const midiLink = Object.assign(document.createElement("a"), { textContent: "Download .mid", download: `${fileName}.mid` }); | |
| const setMidi = () => { midiLink.href = ABC.synth.getMidiFile(tune, { midiOutputType: "encoded", program: Number(instSel.value) }); }; | |
| setMidi(); | |
| tools.append(instBox, abcLink, midiLink); | |
| if (!ABC.synth.supportsAudio()) { | |
| audio.textContent = "This browser has no Web Audio support: use the .mid download."; | |
| return; | |
| } | |
| let lit = []; | |
| const cursor = { | |
| onEvent(ev) { | |
| lit.forEach((el) => el.classList.remove("abcjs-highlight")); | |
| lit = (ev.elements ?? []).flat(); | |
| lit.forEach((el) => el.classList.add("abcjs-highlight")); | |
| }, | |
| onFinished() { lit.forEach((el) => el.classList.remove("abcjs-highlight")); lit = []; }, | |
| }; | |
| const synth = new ABC.synth.SynthController(); | |
| synth.load(audio, cursor, { displayLoop: true, displayRestart: true, displayPlay: true, | |
| displayProgress: true, displayWarp: true }); | |
| await synth.setTune(tune, false, { program: Number(instSel.value) }); | |
| playing = synth; | |
| instSel.addEventListener("change", async () => { | |
| synth.pause(); | |
| await synth.setTune(tune, false, { program: Number(instSel.value) }); | |
| setMidi(); | |
| }); | |
| } | |