Spaces:
Running
Running
Commit ·
310419f
1
Parent(s): 2fa4e1a
enhanced logs
Browse files
app.py
CHANGED
|
@@ -17,6 +17,8 @@ import os
|
|
| 17 |
import re
|
| 18 |
import time
|
| 19 |
import json
|
|
|
|
|
|
|
| 20 |
|
| 21 |
import gradio as gr
|
| 22 |
|
|
@@ -44,11 +46,58 @@ INSTRUMENTS = _STYLES['instruments']
|
|
| 44 |
_GEN = None
|
| 45 |
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
def get_generator ():
|
| 48 |
'''Lazily build the (heavy) ONNX generator on first use.'''
|
| 49 |
global _GEN
|
| 50 |
if _GEN is None:
|
|
|
|
|
|
|
| 51 |
_GEN = StreamingLilyletGenerator(MODEL_DIR, ASSET_DIR)
|
|
|
|
| 52 |
return _GEN
|
| 53 |
|
| 54 |
|
|
@@ -103,6 +152,26 @@ def sync_prompt (composer, genre, instrument, current):
|
|
| 103 |
return '\n'.join(lines)
|
| 104 |
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
def run_generation (prompt, measures, temperature, max_patches, seed, store, top_k=0, top_p=0.9):
|
| 107 |
'''Streaming generate callback. Yields updates for (log, editor, file_list, store).
|
| 108 |
|
|
@@ -111,24 +180,48 @@ def run_generation (prompt, measures, temperature, max_patches, seed, store, top
|
|
| 111 |
|
| 112 |
top_k / top_p have fixed defaults (no UI controls); pass them explicitly to override.
|
| 113 |
'''
|
| 114 |
-
gen = get_generator()
|
| 115 |
meas = int(measures) if measures and int(measures) > 0 else None
|
| 116 |
store = dict(store or {})
|
|
|
|
|
|
|
| 117 |
|
| 118 |
raw = pretty = ''
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
# finished: persist the document, refresh the file list, select the new entry
|
| 126 |
label = OUTPUT_PREFIX + time.strftime('%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
|
| 127 |
store[label] = pretty
|
| 128 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 129 |
-
|
|
|
|
| 130 |
f.write(pretty)
|
| 131 |
-
|
|
|
|
| 132 |
|
| 133 |
|
| 134 |
def load_file (label, store):
|
|
@@ -164,6 +257,12 @@ CUSTOM_CSS = '''
|
|
| 164 |
white-space: nowrap;
|
| 165 |
vertical-align: middle;
|
| 166 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
'''
|
| 168 |
|
| 169 |
|
|
|
|
| 17 |
import re
|
| 18 |
import time
|
| 19 |
import json
|
| 20 |
+
import logging
|
| 21 |
+
from collections import deque
|
| 22 |
|
| 23 |
import gradio as gr
|
| 24 |
|
|
|
|
| 46 |
_GEN = None
|
| 47 |
|
| 48 |
|
| 49 |
+
# ---- system log capture ----------------------------------------------------
|
| 50 |
+
# A ring buffer that mirrors Python logging (lifecycle messages, warnings, and
|
| 51 |
+
# errors) into the Logs panel, alongside the streamed generation text.
|
| 52 |
+
|
| 53 |
+
LOG = logging.getLogger('lilyscript')
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class _RingBufferHandler (logging.Handler):
|
| 57 |
+
'''Keep the most recent N log records as formatted strings.'''
|
| 58 |
+
def __init__ (self, capacity=400):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.records = deque(maxlen=capacity)
|
| 61 |
+
|
| 62 |
+
def emit (self, record):
|
| 63 |
+
try:
|
| 64 |
+
self.records.append(self.format(record))
|
| 65 |
+
except Exception:
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
def text (self):
|
| 69 |
+
return '\n'.join(self.records)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
_LOG_BUFFER = _RingBufferHandler()
|
| 73 |
+
_LOG_BUFFER.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%H:%M:%S'))
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _init_logging ():
|
| 77 |
+
'''Route app + library logs and Python warnings into the ring buffer (once),
|
| 78 |
+
and also echo to stderr so the terminal keeps a copy.'''
|
| 79 |
+
logging.captureWarnings(True)
|
| 80 |
+
root = logging.getLogger()
|
| 81 |
+
if _LOG_BUFFER not in root.handlers:
|
| 82 |
+
root.addHandler(_LOG_BUFFER)
|
| 83 |
+
stderr_h = logging.StreamHandler()
|
| 84 |
+
stderr_h.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s', datefmt='%H:%M:%S'))
|
| 85 |
+
root.addHandler(stderr_h)
|
| 86 |
+
root.setLevel(logging.INFO)
|
| 87 |
+
LOG.setLevel(logging.INFO)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
_init_logging()
|
| 91 |
+
|
| 92 |
+
|
| 93 |
def get_generator ():
|
| 94 |
'''Lazily build the (heavy) ONNX generator on first use.'''
|
| 95 |
global _GEN
|
| 96 |
if _GEN is None:
|
| 97 |
+
LOG.info('loading ONNX generator from %s ...', MODEL_DIR)
|
| 98 |
+
t0 = time.perf_counter()
|
| 99 |
_GEN = StreamingLilyletGenerator(MODEL_DIR, ASSET_DIR)
|
| 100 |
+
LOG.info('generator ready (%.1fs)', time.perf_counter() - t0)
|
| 101 |
return _GEN
|
| 102 |
|
| 103 |
|
|
|
|
| 152 |
return '\n'.join(lines)
|
| 153 |
|
| 154 |
|
| 155 |
+
# Marker line written to the log buffer at the moment generation output begins.
|
| 156 |
+
# `_log_panel` replaces this marker with the live generation text, so the streamed
|
| 157 |
+
# output appears in true chronological position — after the "requested"/"ready"
|
| 158 |
+
# lines and before the later "timing"/"done" lines.
|
| 159 |
+
_GEN_MARKER = '__GENERATION_OUTPUT__'
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def _log_panel (raw=''):
|
| 163 |
+
'''Render the Logs panel in chronological order: the captured system log,
|
| 164 |
+
with the generation-output marker (if present) expanded to the live text.'''
|
| 165 |
+
sys_log = _LOG_BUFFER.text()
|
| 166 |
+
if _GEN_MARKER in sys_log:
|
| 167 |
+
block = '--- generation output ---\n' + raw if raw else '--- generation output ---'
|
| 168 |
+
return sys_log.replace(_GEN_MARKER, block)
|
| 169 |
+
# no marker yet (e.g. before generation): fall back to appending raw
|
| 170 |
+
if raw:
|
| 171 |
+
return (sys_log + '\n' if sys_log else '') + '--- generation output ---\n' + raw
|
| 172 |
+
return sys_log
|
| 173 |
+
|
| 174 |
+
|
| 175 |
def run_generation (prompt, measures, temperature, max_patches, seed, store, top_k=0, top_p=0.9):
|
| 176 |
'''Streaming generate callback. Yields updates for (log, editor, file_list, store).
|
| 177 |
|
|
|
|
| 180 |
|
| 181 |
top_k / top_p have fixed defaults (no UI controls); pass them explicitly to override.
|
| 182 |
'''
|
|
|
|
| 183 |
meas = int(measures) if measures and int(measures) > 0 else None
|
| 184 |
store = dict(store or {})
|
| 185 |
+
LOG.info('generation requested: measures=%s temperature=%s max_patches=%s seed=%s',
|
| 186 |
+
meas, temperature, max_patches, seed)
|
| 187 |
|
| 188 |
raw = pretty = ''
|
| 189 |
+
n_yields = 0
|
| 190 |
+
t0 = time.perf_counter()
|
| 191 |
+
try:
|
| 192 |
+
gen = get_generator()
|
| 193 |
+
# drop a marker in the log timeline; _log_panel expands it to the live output,
|
| 194 |
+
# so subsequent log lines (timing/done) land *after* the generation text.
|
| 195 |
+
LOG.info(_GEN_MARKER)
|
| 196 |
+
for raw, pretty, done in gen.generate_stream(
|
| 197 |
+
prompt_text=prompt or '', max_patches=int(max_patches), temperature=float(temperature),
|
| 198 |
+
top_k=int(top_k), top_p=float(top_p), measures=meas, seed=int(seed)):
|
| 199 |
+
if not done:
|
| 200 |
+
n_yields += 1
|
| 201 |
+
# stream: update log (chronological system log + inline output) + editor
|
| 202 |
+
yield _log_panel(raw), pretty, gr.update(), store
|
| 203 |
+
except Exception as e:
|
| 204 |
+
LOG.exception('generation failed: %s', e)
|
| 205 |
+
yield _log_panel(raw), pretty, gr.update(), store
|
| 206 |
+
return
|
| 207 |
+
|
| 208 |
+
# timing: the stream yields once for prefill + once per generated patch, so the
|
| 209 |
+
# patch count is the non-done yields minus that initial prefill yield.
|
| 210 |
+
elapsed = time.perf_counter() - t0
|
| 211 |
+
n_patches = max(0, n_yields - 1)
|
| 212 |
+
per_patch = (elapsed / n_patches) if n_patches else 0.0
|
| 213 |
+
LOG.info('timing: %.2fs total, %d patches, %.3fs/patch (%.1f patches/s)',
|
| 214 |
+
elapsed, n_patches, per_patch, (n_patches / elapsed if elapsed else 0.0))
|
| 215 |
|
| 216 |
# finished: persist the document, refresh the file list, select the new entry
|
| 217 |
label = OUTPUT_PREFIX + time.strftime('%H%M%S') + ('_m%d' % meas if meas else '') + '_s%d' % int(seed)
|
| 218 |
store[label] = pretty
|
| 219 |
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 220 |
+
out_path = os.path.join(OUTPUT_DIR, label.replace(OUTPUT_PREFIX, '') + '.lyl')
|
| 221 |
+
with open(out_path, 'w', encoding='utf-8') as f:
|
| 222 |
f.write(pretty)
|
| 223 |
+
LOG.info('generation done: %d chars -> %s', len(pretty), os.path.basename(out_path))
|
| 224 |
+
yield _log_panel(raw), pretty, gr.update(choices=list(store.keys()), value=label), store
|
| 225 |
|
| 226 |
|
| 227 |
def load_file (label, store):
|
|
|
|
| 257 |
white-space: nowrap;
|
| 258 |
vertical-align: middle;
|
| 259 |
}
|
| 260 |
+
/* Score List: fixed height with an auto scrollbar, matching the editor's
|
| 261 |
+
18-line viewport (gr.Code lines=18 ≈ 266px). */
|
| 262 |
+
.score-list {
|
| 263 |
+
max-height: 324px;
|
| 264 |
+
overflow-y: auto;
|
| 265 |
+
}
|
| 266 |
'''
|
| 267 |
|
| 268 |
|