import io import json import re import psycopg from psycopg.rows import dict_row from pprint import pprint import openai import queue from contextlib import contextmanager from concurrent.futures import ThreadPoolExecutor, as_completed import sys import threading from tqdm import tqdm slots = queue.Queue() hosts = [ 'localhost:8080', 'localhost:8080', 'localhost:8081', 'localhost:8081', 'localhost:8082', '192.168.88.16:8082' ] for h in hosts: slots.put( openai.OpenAI( base_url=f"http://{h}/v1", api_key="sk-XWptxvNyY6SLEnhD" ) ) num_backends = len(hosts) @contextmanager def lease_slot(): slot = slots.get() try: yield slot finally: slots.put(slot) dbconnstr = "host=localhost dbname=composer_new_dataset_v3" # with open("representative_file_descriptions.txt", "r") as f: # file_ids = [int(line.strip()) for line in f] def control_to_space(text): return "".join(c if c.isprintable() else " " for c in text).strip() def prepare_file(file_id): with psycopg.connect(dbconnstr, row_factory=dict_row) as conn, conn.cursor() as cur: cur.execute( "SELECT dataset_name, metadata FROM files WHERE id = %s", (file_id,) ) file = cur.fetchone() cur.execute( "SELECT type, description FROM file_descriptions WHERE file_id = %s AND type IN ('file_name', 'imslp_text', 'musescore_text')", (file_id,), ) file_descs = cur.fetchall() cur.execute("SELECT metadata FROM instruments WHERE file_id = %s", (file_id,)) instrument_metas = [m["metadata"] for m in cur.fetchall()] cur.execute( "SELECT name, lyrics, texts FROM tracks WHERE file_id = %s", (file_id,) ) tracks = cur.fetchall() prompt_suf = f"Dataset name: {file['dataset_name']}\n" file_names = [ control_to_space(f["description"]) for f in file_descs if f["type"] == "file_name" and control_to_space(f["description"]) != "" ] if file_names: prompt_suf += "File names (aliases):\n " + "\n ".join(file_names) + "\n" file_metadata = file["metadata"] if file_metadata is None: file_metadata = {} mu_meta = file_metadata.get("meta", {}) if "composer" in mu_meta: prompt_suf += f"Composer: {control_to_space(mu_meta['composer'])}\n" if "workTitle" in mu_meta: prompt_suf += f"Work title: {control_to_space(mu_meta['workTitle'])}\n" mu_descs = [ control_to_space(f["description"]) for f in file_descs if f["type"] == "musescore_text" and control_to_space(f["description"]) != "" ] if mu_descs: prompt_suf += ( "Musescore extended metadata:\n " + "\n ".join(mu_descs) + "\n" ) track_names = [ control_to_space(t["name"]) for t in tracks if t.get("name", None) is not None and control_to_space(t["name"]) != "" ] instrument_meta = [ ( control_to_space( m.get("instrument") + ( (" (" + m.get("instrumentId") + ")") if "instrumentId" in m else "" ) ) ) for m in instrument_metas if m is not None and "instrument" in m ] if instrument_meta: prompt_suf += ( "Instrument metadata:\n " + "\n ".join(f"{i+1}. {m}" for i, m in enumerate(instrument_meta)) + "\n" ) elif track_names: prompt_suf += ( "Track names:\n " + "\n ".join(f"{i+1}. {t}" for i, t in enumerate(track_names)) + "\n" ) track_lyrics = "\n".join( [ control_to_space(line) for t in tracks if t["lyrics"] is not None for line in t["lyrics"].splitlines() if control_to_space(line) != "" ] ) if track_lyrics: prompt_suf += ( "Track lyrics (first 500 bytes):\n " + "\n ".join(track_lyrics[:500].split("\n")) + "\n" ) # print("File:") # pprint(file) # print("File descriptions:") # pprint(file_descs) # print("Instrument metadata:") # pprint(instrument_metas) # print("Track metadata:") # pprint(tracks) # print("---\n\n") # prompt = "Write 5 short English prompts that will be used to train a generative AI model to produce short segments of symbolic music. " # prompt += "The prompts should be specific to a training example (a MIDI file or musical score), metadata for which is provided below. " # prompt += "They should be phrased as a user would enter them into a single-line text input: terse, not as full sentences. " # prompt += "Based on the metadata, infer the type of music, the composer, and the title of the work. The prompts should use any relevant " # prompt += "information from the metadata to guide the AI model in generating music segments, including artist name, work title, genre, and " # prompt += "stylistic / emotional descriptors. Not all metadata is accurate, but the majority of it is correct for a given file. " # prompt += "Be careful drawing inference from file names, as they may not be accurate. Use a majority vote approach when inferring the " # prompt += "identity of the work from file names, and always prefer metadata from within the file over file names when available. " # prompt += "Briefly describe your reasoning for each prompt. " # prompt += "\n\n" prompt = "In several steps, we will use metadata extracted from a symbolic music file to create a set of English text prompts " prompt += "that will be used to train a music-generating AI model. The metadata may include information about the composer, " prompt += "the title of the work, the genre, and stylistic or emotional descriptors. Not all metadata will be accurate. In particular, " prompt += 'a minority of file names under the optional "File names (aliases)" section may contain errors. Prefer to draw inferences ' prompt += 'from metadata in other sections, or use a majority vote approach when inferring the identity of the work from "File names (aliases)". ' prompt += "First, try to infer the type of music, the composer/artist/performer, the title of the work, and any relevant emotional or stylistic attributes from the metadata provided. " prompt += "You need not generate any prompts or ask follow-up questions at this stage. " prompt += "Here is the metadata extracted from the file:\n\n" return file_id, prompt + prompt_suf def ai_file(file_id, prompt0): m0 = [ { "role": "system", "content": "You are an AI assistant for machine learning research. Your top priority is to produce accurate, well-reasoned responses that satisfy user requests.", }, {"role": "user", "content": prompt0}, ] with lease_slot() as client: completion = client.chat.completions.create( model="gpt-3.5-turbo", extra_body={"mirostat": 2}, messages=m0, ) m1 = m0 + [completion.choices[0].message] m1 += [ { "role": "user", "content": "".join( [ "Now, generate 5 distinct prompts for the file. ", 'Each prompt should be terse, as a user would enter in a single-line prompt input text box, for example: "groovy upbeat bassline michael jackson". ', "Write the prompts separated by newlines and without any preamble, prefix, or formatting. ", ] ), } ] completion = client.chat.completions.create( model="gpt-3.5-turbo", extra_body={"mirostat": 2}, messages=m1, ) prompts = [ re.sub(r"^\s*[0-9]+[.):]", "", line).replace("<|eot_id|>", "").strip() for line in completion.choices[0].message.content.splitlines() ] prompts = [p for p in prompts if p != ""] return file_id, prompts script_start_timestamp = 1716949249 ai_q = queue.Queue(maxsize=100) result_q = queue.Queue(maxsize=100) def prep_thd_entry(): try: with psycopg.connect(dbconnstr, row_factory=dict_row) as conn, conn.cursor() as cur: cur.execute( """ SELECT id FROM files WHERE EXISTS (SELECT FROM extracted_clips WHERE file_id = files.id AND symbolic_length IS NOT NULL) AND NOT EXISTS (SELECT FROM file_descriptions WHERE file_id = files.id AND type = 'llama_prompt') AND dataset_name <> 'imslp' ORDER BY rand_order DESC """ ) for row in cur: try: ai_q.put(prepare_file(row["id"])) except Exception as e: print(f'prep_thd: {e}') finally: for _ in range(num_backends): ai_q.put(None) def write_thd_entry(): dead_thds = 0 while True: try: x = result_q.get() if x is None: dead_thds += 1 if dead_thds == num_backends: break file_id, prompts = x print(f'{file_id}: {prompts}') with psycopg.connect(dbconnstr, row_factory=dict_row) as conn: conn.autocommit = True with conn.transaction(), conn.cursor() as cur: cur.executemany( "INSERT INTO file_descriptions ( file_id, script_start_timestamp, type, description ) VALUES (%s, %s, 'llama_prompt', %s)", ((file_id, script_start_timestamp, p) for p in prompts), ) except Exception as e: print(f'write_thd: {e}') def ai_thd_entry(): while True: x = ai_q.get() if x is None: result_q.put(None) break try: result_q.put(ai_file(*x)) except Exception as e: print(f'ai_thd: {e}') prep_thd = threading.Thread(target=prep_thd_entry, daemon=True) prep_thd.start() write_thd = threading.Thread(target=write_thd_entry, daemon=True) write_thd.start() ai_thds = [] for _ in range(num_backends): ai_thds.append(threading.Thread(target=ai_thd_entry, daemon=True)) ai_thds[-1].start() prep_thd.join() for t in ai_thds: t.join() write_thd.join() # Remove all control characters from the text # bitmidi, geocities, lmd_full: # file_descriptions -> file_name # a file may have multiple file_name descriptions, some are wrong, but usually the majority are correct for a given file # track_metadata -> name, lyrics, texts # texts -> kind in (marker, ...) # some files have name and author info encoded in track names # imslp: # just file_descriptions -> imslp_text # musescore: # file -> metadata -> meta -> composer, workTitle # file_descriptions -> musescore_text # instrument -> metadata -> instrument, instrumentId