Jacobina / api /logging_run.py
marinarosa's picture
initial commit
be82719
Raw
History Blame Contribute Delete
7.76 kB
"""Generate view (miru's Logging Mode): bulk generation with probability logging.
Single-user server-side run state (tracer + originating inputs) lives in
``state.get_logging_session()`` — the streaming handler updates it so Stop and
Continue can reach the live tracer, and Continue is invalidated when the model
changes (object identity, like upstream).
"""
from __future__ import annotations
import time
from collections.abc import Iterator
from api.helpers import ChatValidationError, ui_sampling_params
from api.interactive import _reset_tracer_for_mode
from api.models import model_manager
from api.serialize import error_payload, fig_json
from api.state import get_logging_session
from miru_tracer.core.logging_config import get_logger
from miru_tracer.core.tracer import LLMTracer
from miru_tracer.visualization.plots import (
get_generation_stats,
plot_probability_visualizations,
)
logger = get_logger(__name__)
def _finalize(tracer, params, heatmap_ranks, prob_mode) -> dict:
"""Stats, plots, and export for a finished (or stopped) run."""
stats = get_generation_stats(tracer.history)
ranks = int(heatmap_ranks) if heatmap_ranks else 10
if tracer.history:
ranks = min(ranks, len(tracer.history[0].top_k_tokens))
figures = plot_probability_visualizations(
tracer.history,
top_k=ranks,
probability_mode=prob_mode,
temperature=params.temperature,
)
return {
"stats": stats,
"fig_heatmap": fig_json(figures[0]) if figures else None,
"fig_confidence": fig_json(figures[1]) if len(figures) > 1 else None,
"export": tracer.export_to_dict(params),
}
def _stream_run(
tracer, params, originals, max_new_tokens, log_top_k, log_full_probs,
stop_at_eos, heatmap_ranks, prob_mode, start_time,
) -> Iterator[dict]:
"""Shared streaming loop for generate and continue."""
tokens_before = len(tracer.history)
for step_data in tracer.generate_stream(
max_new_tokens=int(max_new_tokens),
params=params,
log_top_k=max(int(log_top_k or 10), 1),
log_full_probs=bool(log_full_probs),
stop_at_eos=bool(stop_at_eos),
):
yield {
"ok": True,
"type": "progress",
"text": tracer.get_generated_text(),
"progress": {
"step": len(tracer.history),
"new_tokens": len(tracer.history) - tokens_before,
"last_token": step_data.token_text,
},
}
final = _finalize(tracer, params, heatmap_ranks, prob_mode)
logger.info(
f"Generation finished: {len(tracer.history)} tokens in "
f"{time.time() - start_time:.2f}s"
)
yield {
"ok": True,
"type": "final",
"text": tracer.get_generated_text(),
"originals": list(originals),
**final,
}
def logging_generate(
mode: str, prompt: str, chat_json: str, raw_text: str,
thinking: str, think_prefill: str,
max_new_tokens: int, strategy: str, temperature: float,
top_k: int, top_p: float,
stop_at_eos: bool, log_top_k: int, heatmap_ranks: int,
log_full_probs: bool, prob_mode: str,
) -> Iterator[dict]:
model = model_manager.get_model()
tokenizer = model_manager.get_tokenizer()
device = model_manager.get_device()
run = get_logging_session()
if model is None or tokenizer is None:
yield error_payload("No model loaded. Load one in the Model view first.")
return
if max_new_tokens is None or max_new_tokens < 1:
yield error_payload("Maximum new tokens must be at least 1")
return
start_time = time.time()
try:
params = ui_sampling_params(strategy, temperature, top_k, top_p)
tracer = LLMTracer(model, tokenizer, device)
_reset_tracer_for_mode(
tracer, mode, prompt, chat_json, raw_text, thinking, think_prefill
)
originals = (mode, prompt, chat_json, raw_text, thinking, think_prefill)
with run.lock:
run.tracer = tracer # reachable by Stop mid-run
run.originals = originals
logger.info(
f"Logging generation started: mode={mode}, "
f"max_tokens={max_new_tokens}, strategy={strategy}"
)
yield from _stream_run(
tracer, params, originals, max_new_tokens, log_top_k,
log_full_probs, stop_at_eos, heatmap_ranks, prob_mode, start_time,
)
except ChatValidationError as e:
with run.lock:
run.tracer = None
run.originals = None
yield error_payload(str(e))
except Exception as e:
logger.error(f"Generation error: {e}", exc_info=True)
with run.lock:
run.tracer = None
run.originals = None
yield error_payload(f"Error during generation:\n\n{e}", trace=True)
def logging_continue(
max_new_tokens: int, strategy: str, temperature: float,
top_k: int, top_p: float,
stop_at_eos: bool, log_top_k: int, heatmap_ranks: int,
log_full_probs: bool, prob_mode: str,
) -> Iterator[dict]:
"""Resume the same tracer; history accumulates across runs."""
run = get_logging_session()
with run.lock:
tracer = run.tracer
originals = run.originals
if tracer is None:
yield error_payload("No previous generation to continue from.")
return
if max_new_tokens is None or max_new_tokens < 1:
yield error_payload("Maximum new tokens must be at least 1")
return
if model_manager.get_model() is None or tracer.model is not model_manager.get_model():
yield error_payload(
"Model has been unloaded or changed.\n\nPlease start a new generation."
)
return
start_time = time.time()
try:
params = ui_sampling_params(strategy, temperature, top_k, top_p)
logger.info(f"Continuing generation: max_tokens={max_new_tokens}")
yield from _stream_run(
tracer, params, originals, max_new_tokens, log_top_k,
log_full_probs, stop_at_eos, heatmap_ranks, prob_mode, start_time,
)
except Exception as e:
logger.error(f"Continuation error: {e}", exc_info=True)
yield error_payload(f"Error during continuation:\n\n{e}", trace=True)
def logging_stop() -> dict:
"""Cooperative stop; the running stream finalizes the partial run itself."""
run = get_logging_session()
with run.lock:
tracer = run.tracer
if tracer is None:
return error_payload("No generation running.")
tracer.request_stop()
logger.info(f"Stop requested after {len(tracer.history)} tokens")
return {"ok": True}
def logging_replot(heatmap_ranks: int, prob_mode: str, temperature: float) -> dict:
"""Re-render plots with new display settings — no regeneration."""
run = get_logging_session()
with run.lock:
tracer = run.tracer
if tracer is None or not tracer.history:
return {"ok": True, "fig_heatmap": None, "fig_confidence": None}
try:
ranks = min(
int(heatmap_ranks) if heatmap_ranks else 10,
len(tracer.history[0].top_k_tokens),
)
figures = plot_probability_visualizations(
tracer.history,
top_k=ranks,
probability_mode=prob_mode,
temperature=float(temperature),
)
return {
"ok": True,
"fig_heatmap": fig_json(figures[0]) if figures else None,
"fig_confidence": fig_json(figures[1]) if len(figures) > 1 else None,
}
except Exception as e:
logger.error(f"Error refreshing visualizations: {e}", exc_info=True)
return error_payload(str(e))