| |
| """Cached byte generation for two- and three-level nested Mamba checkpoints.""" |
| from __future__ import annotations |
|
|
| import argparse |
| import base64 |
| import gc |
| import io |
| import json |
| import time |
| from pathlib import Path |
| from typing import Dict, List, Optional |
|
|
| import torch |
|
|
| from nested_inference_tools import generate_bytes, load_nested_checkpoint |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "--checkpoint", |
| default=".", |
| help="Hugging Face model directory (default: current directory) or legacy last.pt.", |
| ) |
| prompt = parser.add_mutually_exclusive_group() |
| prompt.add_argument("--prompt", default="", help="UTF-8 prompt text.") |
| prompt.add_argument("--prompt-file", help="Read prompt bytes from this file.") |
| parser.add_argument("--max-new-bytes", type=int, default=256) |
| parser.add_argument("--temperature", type=float, default=0.8) |
| parser.add_argument("--top-p", type=float, default=0.9) |
| parser.add_argument("--top-k", type=int, default=0) |
| parser.add_argument("--repeat-penalty", type=float, default=1.05) |
| parser.add_argument("--repeat-window", type=int, default=256) |
| parser.add_argument("--greedy", action="store_true") |
| parser.add_argument("--seed", type=int, default=1234) |
| parser.add_argument( |
| "--precision", |
| choices=["fp16", "bf16", "fp32"], |
| default="bf16", |
| help=( |
| "Model weight/activation precision. BF16 is the safe default for " |
| "Mamba-2's long cached rollouts; use FP16 only for checkpoints and " |
| "GPUs verified to remain finite." |
| ), |
| ) |
| parser.add_argument("--device", default="cuda:0", help="Single-device inference target.") |
| parser.add_argument("--fine-device", default=None) |
| parser.add_argument("--nested-devices", default=None) |
| parser.add_argument("--tertiary-device", default=None) |
| parser.add_argument("--use-saved-placement", action="store_true") |
| parser.add_argument("--output", help="Optional raw-byte output file.") |
| parser.add_argument("--stats-json", help="Optional JSON statistics output.") |
| parser.add_argument( |
| "--image-output-dir", |
| help=( |
| "Extract complete generated P6 PPM images, convert them to PNG, and " |
| "write them to this directory. Images are also embedded in --html-output." |
| ), |
| ) |
| parser.add_argument("--html-output", "--output-html", dest="html_output", help="Write a self-contained prompt/response report with toggleable L2/L3 influence coloring.") |
| return parser.parse_args() |
|
|
|
|
| def _ppm_token(data: bytes, position: int) -> tuple[bytes, int]: |
| """Read one whitespace/comment-delimited PPM header token.""" |
| size = len(data) |
| while position < size: |
| if data[position] in b" \t\r\n": |
| position += 1 |
| continue |
| if data[position] == ord("#"): |
| newline = data.find(b"\n", position) |
| if newline < 0: |
| raise ValueError("unterminated PPM header comment") |
| position = newline + 1 |
| continue |
| break |
| start = position |
| while position < size and data[position] not in b" \t\r\n#": |
| position += 1 |
| if position == start: |
| raise ValueError("missing PPM header token") |
| return data[start:position], position |
|
|
|
|
| def extract_ppm_images(data: bytes) -> tuple[List[Dict[str, object]], List[str]]: |
| """Extract complete P6 RGB images from an arbitrary generated byte stream.""" |
| try: |
| from PIL import Image |
| except ImportError as error: |
| return [], [f"Pillow is required to render generated images: {error}"] |
|
|
| images: List[Dict[str, object]] = [] |
| warnings: List[str] = [] |
| search_from = 0 |
| while len(images) < 32: |
| start = data.find(b"P6", search_from) |
| if start < 0: |
| break |
| search_from = start + 2 |
| if start > 0 and data[start - 1] not in b" \t\r\n>": |
| continue |
| if start + 2 >= len(data) or data[start + 2] not in b" \t\r\n": |
| continue |
| try: |
| width_token, position = _ppm_token(data, start + 2) |
| height_token, position = _ppm_token(data, position) |
| maximum_token, position = _ppm_token(data, position) |
| width, height, maximum = int(width_token), int(height_token), int(maximum_token) |
| if width < 1 or height < 1 or width * height > 16_777_216: |
| raise ValueError(f"unsafe dimensions {width}x{height}") |
| if maximum != 255: |
| raise ValueError(f"unsupported maximum channel value {maximum}; expected 255") |
| if position >= len(data) or data[position] not in b" \t\r\n": |
| raise ValueError("missing whitespace before PPM pixel payload") |
| |
| |
| pixel_start = position + 1 |
| if data[position] == ord("\r") and pixel_start < len(data) and data[pixel_start] == ord("\n"): |
| pixel_start += 1 |
| pixel_bytes = width * height * 3 |
| pixel_end = pixel_start + pixel_bytes |
| if pixel_end > len(data): |
| warnings.append( |
| f"Incomplete PPM at byte {start}: {width}x{height} needs " |
| f"{pixel_bytes:,} RGB bytes, but only {max(0, len(data)-pixel_start):,} remain." |
| ) |
| continue |
| image = Image.frombytes("RGB", (width, height), data[pixel_start:pixel_end]) |
| encoded = io.BytesIO() |
| image.save(encoded, format="PNG", optimize=True) |
| images.append( |
| { |
| "width": width, |
| "height": height, |
| "start": start, |
| "end": pixel_end, |
| "png": encoded.getvalue(), |
| } |
| ) |
| search_from = pixel_end |
| except (TypeError, ValueError) as error: |
| warnings.append(f"Invalid PPM candidate at byte {start}: {error}.") |
| return images, warnings |
|
|
|
|
| def image_report_payload(images: List[Dict[str, object]], warnings: List[str]) -> Dict[str, object]: |
| return { |
| "images": [ |
| { |
| "width": int(item["width"]), |
| "height": int(item["height"]), |
| "start": int(item["start"]), |
| "end": int(item["end"]), |
| "data_uri": "data:image/png;base64," + base64.b64encode(item["png"]).decode("ascii"), |
| } |
| for item in images |
| ], |
| "warnings": list(warnings), |
| } |
|
|
|
|
| def write_extracted_images( |
| directory: Path, images: List[Dict[str, object]], *, prefix: str |
| ) -> List[str]: |
| directory.mkdir(parents=True, exist_ok=True) |
| paths: List[str] = [] |
| for index, item in enumerate(images, 1): |
| path = directory / f"{prefix}_{index:03d}_{item['width']}x{item['height']}.png" |
| path.write_bytes(item["png"]) |
| paths.append(str(path)) |
| return paths |
|
|
|
|
| def attributed_utf8_segments( |
| data: bytes, attributions: List[Dict[str, object]] |
| ) -> List[Dict[str, object]]: |
| """Group byte attribution into valid UTF-8 characters without losing data.""" |
| segments: List[Dict[str, object]] = [] |
| index = 0 |
| while index < len(data): |
| first = data[index] |
| width = ( |
| 1 |
| if first < 0x80 |
| else 2 |
| if 0xC2 <= first <= 0xDF |
| else 3 |
| if 0xE0 <= first <= 0xEF |
| else 4 |
| if 0xF0 <= first <= 0xF4 |
| else 1 |
| ) |
| chunk = data[index : index + width] |
| try: |
| text = chunk.decode("utf-8", "strict") |
| except UnicodeDecodeError: |
| width = 1 |
| chunk = data[index : index + 1] |
| text = chunk.decode("utf-8", "replace") |
| values = attributions[index : index + width] |
| count = max(1, len(values)) |
| segments.append( |
| { |
| "text": text, |
| "bytes": list(chunk), |
| "level2_active": any(bool(item.get("level2_active")) for item in values), |
| "level3_active": any(bool(item.get("level3_active")) for item in values), |
| "level2_delta_logp": sum( |
| float(item.get("level2_delta_logp", 0.0)) for item in values |
| ) |
| / count, |
| "level3_delta_logp": sum( |
| float(item.get("level3_delta_logp", 0.0)) for item in values |
| ) |
| / count, |
| "hierarchy_delta_logp": sum( |
| float(item.get("hierarchy_delta_logp", 0.0)) for item in values |
| ) |
| / count, |
| } |
| ) |
| index += width |
| return segments |
|
|
|
|
| def make_generation_html( |
| prompt: bytes, |
| generated: bytes, |
| attributions: List[Dict[str, object]], |
| stats: Dict[str, object], |
| comparisons: Optional[List[Dict[str, object]]] = None, |
| image_report: Optional[Dict[str, object]] = None, |
| ) -> str: |
| segments = attributed_utf8_segments(generated, attributions) |
| payload = json.dumps( |
| { |
| "prompt": prompt.decode("utf-8", "replace"), |
| "segments": segments, |
| "stats": stats, |
| "comparisons": comparisons or [], |
| "image_report": image_report or {"images": [], "warnings": []}, |
| }, |
| separators=(",", ":"), |
| ).replace("</", "<\\/") |
| return f"""<!doctype html> |
| <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> |
| <title>Nested Mamba generation influence</title> |
| <style> |
| :root{{--bg:#090d17;--panel:#111a2b;--line:#273652;--text:#e7edf9;--muted:#91a0b8;--prompt:#d6deec;--response:#63a4ff;--fine:#51d6ca;--l2:#ffbe55;--l3:#a78bfa;--negative:#fb7185}} |
| *{{box-sizing:border-box}}body{{margin:0;background:linear-gradient(145deg,#080c15,#11192b);color:var(--text);font:14px/1.5 system-ui,sans-serif}}main{{max-width:1200px;margin:auto;padding:28px}} |
| h1{{margin:0 0 6px;font-size:27px}}p{{color:var(--muted)}}.toolbar,.panel{{background:#111a2bf2;border:1px solid var(--line);border-radius:12px}} |
| .toolbar{{display:flex;align-items:center;gap:18px;flex-wrap:wrap;padding:13px 16px;margin:18px 0}}label{{display:flex;align-items:center;gap:8px;font-weight:650}}input{{accent-color:var(--l3)}} |
| .legend{{display:flex;gap:14px;flex-wrap:wrap;color:var(--muted);font-size:12px}}.dot{{width:10px;height:10px;border-radius:50%;display:inline-block;margin-right:5px}} |
| .panel{{padding:20px}}.text{{white-space:pre-wrap;overflow-wrap:anywhere;font:16px/1.65 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}}.responses{{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:16px}}.response-card{{min-width:0;background:#0c1321;border:1px solid var(--line);border-radius:10px;padding:14px}}.response-card h2{{font:700 14px system-ui,sans-serif;margin:0 0 4px}}.response-meta{{color:var(--muted);font:11px system-ui,sans-serif;margin-bottom:11px}} |
| .prompt{{color:var(--prompt)}}.generated{{color:var(--response);transition:color .15s,opacity .15s}}.divider{{display:block;color:var(--muted);font:12px system-ui,sans-serif;margin:18px 0 7px;border-top:1px solid var(--line);padding-top:10px}} |
| .cards{{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-top:14px}}.card{{background:#0c1321;border:1px solid var(--line);border-radius:9px;padding:11px}}.name{{color:var(--muted);font-size:11px;text-transform:uppercase}}.value{{font-size:18px;font-weight:700}} |
| .note{{border-left:3px solid var(--l2);padding:9px 12px;background:#ffbe550d}} |
| .image-panel{{margin-top:16px}}.image-galleries{{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}}.image-gallery{{background:#0c1321;border:1px solid var(--line);border-radius:10px;padding:12px;min-width:0}}.image-gallery h2{{font-size:14px;margin:0 0 9px}}.image-item{{margin:0 0 12px}}.image-item img{{display:block;max-width:100%;height:auto;image-rendering:auto;border:1px solid #354766;background:#05070b}}.image-item figcaption,.image-status{{color:var(--muted);font-size:11px;margin-top:5px;white-space:pre-wrap}} |
| @media(max-width:900px){{.responses{{grid-template-columns:1fr}}}} |
| </style></head><body><main> |
| <h1>Nested Mamba generation influence</h1><p id="subtitle"></p> |
| <div class="toolbar"><label><input id="influence" type="checkbox" checked> Color generated text by hierarchy influence</label><div class="legend"><span><i class="dot" style="background:var(--fine)"></i>fine only</span><span><i class="dot" style="background:var(--l2)"></i>L2 dominant</span><span><i class="dot" style="background:var(--l3)"></i>L3 dominant</span><span><i class="dot" style="background:var(--negative)"></i>dominant branch reduced selected-byte probability</span></div></div> |
| <p class="note">Color is a decoder counterfactual, not merely an activation marker. Positive delta means the cached dynamic level increased the selected byte's log probability relative to replacing that level with its beginning-of-document state. Hover text for exact values.</p> |
| <section class="panel"><div class="text"><span class="divider">PROMPT SHARED BY ALL ROLLOUTS</span><span class="prompt" id="prompt"></span></div><div class="responses"><article class="response-card"><h2>Level 1 only</h2><div class="response-meta" id="level1Meta"></div><div class="text generated" id="level1Response"></div></article><article class="response-card"><h2>Levels 1 + 2</h2><div class="response-meta" id="level12Meta"></div><div class="text generated" id="level12Response"></div></article><article class="response-card"><h2>Levels 1 + 2 + 3</h2><div class="response-meta" id="fullMeta"></div><div class="text" id="response"></div></article></div><div class="cards" id="cards"></div></section> |
| <section class="panel image-panel"><h1>Generated image rendering</h1><p>Complete P6 RGB byte sequences found in each prompt-plus-rollout stream are validated and embedded here as PNG.</p><div class="image-galleries"><div class="image-gallery"><h2>Level 1 only</h2><div id="level1Images"></div></div><div class="image-gallery"><h2>Levels 1 + 2</h2><div id="level12Images"></div></div><div class="image-gallery"><h2>Levels 1 + 2 + 3</h2><div id="fullImages"></div></div></div></section> |
| <script> |
| const R={payload},prompt=document.getElementById("prompt"),response=document.getElementById("response"),toggle=document.getElementById("influence"); |
| prompt.textContent=R.prompt;document.getElementById("subtitle").textContent=`${{R.stats.checkpoint}} • step ${{Number(R.stats.checkpoint_step).toLocaleString()}}`; |
| const byMode=Object.fromEntries(R.comparisons.map(x=>[x.mode,x]));for(const [mode,id,color] of [["level1","level1Response","#51d6ca"],["level12","level12Response","#ffbe55"]]){{const item=byMode[mode]||{{text:"",generated_bytes:0,seconds:0}};document.getElementById(id).textContent=item.text;document.getElementById(id).dataset.color=color;document.getElementById(mode+"Meta").textContent=`${{Number(item.generated_bytes).toLocaleString()}} bytes • ${{Number(item.seconds).toFixed(2)}} s`;}}document.getElementById("fullMeta").textContent=`${{Number(R.stats.generated_bytes).toLocaleString()}} bytes • ${{Number(R.stats.generation_seconds_including_prefill).toFixed(2)}} s`; |
| const magnitudes=R.segments.flatMap(x=>[Math.abs(x.level2_delta_logp),Math.abs(x.level3_delta_logp)]).filter(Number.isFinite).sort((a,b)=>a-b),scale=magnitudes[Math.floor(magnitudes.length*.9)]||.01; |
| function classification(x){{const candidates=[];if(x.level2_active)candidates.push([Math.abs(x.level2_delta_logp),x.level2_delta_logp,"#ffbe55","L2"]);if(x.level3_active)candidates.push([Math.abs(x.level3_delta_logp),x.level3_delta_logp,"#a78bfa","L3"]);if(!candidates.length)return ["#51d6ca",.72,"fine only"];candidates.sort((a,b)=>b[0]-a[0]);const best=candidates[0],color=best[1]<0?"#fb7185":best[2],opacity=.48+.52*Math.min(1,best[0]/scale);return [color,opacity,best[1]<0?best[3]+" negative":best[3]+" dominant"]}} |
| R.segments.forEach(x=>{{const span=document.createElement("span"),style=classification(x);span.className="generated";span.textContent=x.text;span.dataset.color=style[0];span.dataset.opacity=style[1];span.title=`${{style[2]}} • bytes ${{x.bytes.join(",")}} • L2 Δlogp ${{x.level2_delta_logp.toFixed(5)}} • L3 Δlogp ${{x.level3_delta_logp.toFixed(5)}} • combined Δlogp ${{x.hierarchy_delta_logp.toFixed(5)}}`;response.appendChild(span)}}); |
| function recolor(){{response.querySelectorAll(".generated").forEach(span=>{{span.style.color=toggle.checked?span.dataset.color:"#63a4ff";span.style.opacity=toggle.checked?span.dataset.opacity:"1"}});for(const id of ["level1Response","level12Response"]){{const node=document.getElementById(id);node.style.color=toggle.checked?node.dataset.color:"#63a4ff"}}}}toggle.addEventListener("change",recolor);recolor(); |
| const attrs=R.segments,mean=key=>attrs.length?attrs.reduce((s,x)=>s+Number(x[key]||0),0)/attrs.length:0,cards=[["Prompt bytes",R.stats.prompt_bytes],["Generated bytes",R.stats.generated_bytes],["Generation tok/s",Number(R.stats.generated_bytes_per_second_including_prefill).toFixed(1)],["Mean L2 Δlogp",mean("level2_delta_logp").toFixed(5)],["Mean L3 Δlogp",mean("level3_delta_logp").toFixed(5)],["Mean combined Δlogp",mean("hierarchy_delta_logp").toFixed(5)]]; |
| document.getElementById("cards").innerHTML=cards.map(x=>`<div class="card"><div class="name">${{x[0]}}</div><div class="value">${{x[1]}}</div></div>`).join(""); |
| function renderImages(target,report){{const root=document.getElementById(target),images=report?.images||[],warnings=report?.warnings||[];if(!images.length&&!warnings.length){{root.innerHTML='<div class="image-status">No complete P6 image detected.</div>';return}}images.forEach((item,index)=>{{const figure=document.createElement("figure");figure.className="image-item";const image=document.createElement("img");image.src=item.data_uri;image.alt=`Generated image ${{index+1}}, ${{item.width}} by ${{item.height}} pixels`;const caption=document.createElement("figcaption");caption.textContent=`Image ${{index+1}} • ${{item.width}}×${{item.height}} • byte range ${{item.start.toLocaleString()}}–${{(item.end-1).toLocaleString()}}`;figure.append(image,caption);root.appendChild(figure)}});if(warnings.length){{const status=document.createElement("div");status.className="image-status";status.textContent=warnings.join("\\n");root.appendChild(status)}}}} |
| renderImages("fullImages",R.image_report);renderImages("level1Images",byMode.level1?.image_report);renderImages("level12Images",byMode.level12?.image_report); |
| </script></main></body></html>""" |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
| if args.max_new_bytes < 0 or args.temperature <= 0 or not 0 < args.top_p <= 1: |
| raise ValueError("max-new-bytes must be non-negative, temperature positive, and top-p in (0, 1]") |
| if args.prompt_file: |
| prompt = Path(args.prompt_file).expanduser().read_bytes() |
| else: |
| prompt = args.prompt.encode("utf-8") |
|
|
| load_started = time.perf_counter() |
| loaded = load_nested_checkpoint( |
| args.checkpoint, |
| precision=args.precision, |
| device=args.device, |
| fine_device=args.fine_device, |
| nested_devices=args.nested_devices, |
| tertiary_device=args.tertiary_device, |
| use_saved_placement=args.use_saved_placement, |
| ) |
| torch.cuda.synchronize() |
| load_seconds = time.perf_counter() - load_started |
| generation_started = time.perf_counter() |
| generated, stream = generate_bytes( |
| loaded, |
| prompt, |
| max_new_bytes=args.max_new_bytes, |
| temperature=args.temperature, |
| top_p=args.top_p, |
| top_k=args.top_k, |
| repeat_penalty=args.repeat_penalty, |
| repeat_window=args.repeat_window, |
| greedy=args.greedy, |
| seed=args.seed, |
| collect_hierarchy_attribution=bool(args.html_output), |
| ) |
| torch.cuda.synchronize() |
| generation_seconds = time.perf_counter() - generation_started |
| combined = prompt + generated |
| full_images, full_image_warnings = extract_ppm_images(combined) |
| full_image_report = image_report_payload(full_images, full_image_warnings) |
| print(combined.decode("utf-8", "replace")) |
| attributions = list(stream.get("generated_attribution", [])) |
| stats = { |
| "checkpoint": str(loaded.checkpoint_path), |
| "checkpoint_step": loaded.checkpoint_step, |
| "trained_tokens": loaded.trained_tokens, |
| "precision": loaded.precision, |
| "model_parallel": loaded.model_parallel, |
| "prompt_bytes": len(prompt), |
| "generated_bytes": len(generated), |
| "load_seconds": load_seconds, |
| "generation_seconds_including_prefill": generation_seconds, |
| "generated_bytes_per_second_including_prefill": ( |
| len(generated) / generation_seconds if generation_seconds else 0.0 |
| ), |
| "fine_patches": int(stream["completed_patches"]), |
| "level2_pools": int(stream["completed_nested_patches"]), |
| "level3_pools": int(stream.get("completed_tertiary_patches", 0)), |
| "hierarchy_attribution": bool(args.html_output), |
| "rendered_images": len(full_images), |
| "image_render_warnings": full_image_warnings, |
| } |
| if attributions: |
| stats.update( |
| { |
| "attributed_bytes": len(attributions), |
| "level2_active_generated_bytes": sum( |
| bool(item.get("level2_active")) for item in attributions |
| ), |
| "level3_active_generated_bytes": sum( |
| bool(item.get("level3_active")) for item in attributions |
| ), |
| "mean_level2_delta_logp": sum( |
| float(item.get("level2_delta_logp", 0.0)) |
| for item in attributions |
| ) |
| / len(attributions), |
| "mean_level3_delta_logp": sum( |
| float(item.get("level3_delta_logp", 0.0)) |
| for item in attributions |
| ) |
| / len(attributions), |
| "mean_hierarchy_delta_logp": sum( |
| float(item.get("hierarchy_delta_logp", 0.0)) |
| for item in attributions |
| ) |
| / len(attributions), |
| } |
| ) |
| comparisons: List[Dict[str, object]] = [] |
| del stream |
| gc.collect() |
| if args.html_output: |
| for mode, label in (("level1", "Level 1 only"), ("level12", "Levels 1 + 2")): |
| comparison_started = time.perf_counter() |
| comparison_bytes, comparison_stream = generate_bytes( |
| loaded, |
| prompt, |
| max_new_bytes=args.max_new_bytes, |
| temperature=args.temperature, |
| top_p=args.top_p, |
| top_k=args.top_k, |
| repeat_penalty=args.repeat_penalty, |
| repeat_window=args.repeat_window, |
| greedy=args.greedy, |
| seed=args.seed, |
| hierarchy_mode=mode, |
| ) |
| torch.cuda.synchronize() |
| comparison_seconds = time.perf_counter() - comparison_started |
| comparisons.append( |
| { |
| "mode": mode, |
| "label": label, |
| "text": comparison_bytes.decode("utf-8", "replace"), |
| "generated_bytes": len(comparison_bytes), |
| "seconds": comparison_seconds, |
| "fine_patches": int(comparison_stream["completed_patches"]), |
| "level2_pools": int( |
| comparison_stream["completed_nested_patches"] |
| ), |
| "level3_pools": int( |
| comparison_stream.get("completed_tertiary_patches", 0) |
| ), |
| "image_report": image_report_payload( |
| *extract_ppm_images(prompt + comparison_bytes) |
| ), |
| } |
| ) |
| del comparison_stream |
| gc.collect() |
| |
| |
| stats["comparison_rollouts"] = [ |
| {key: value for key, value in item.items() if key != "image_report"} |
| for item in comparisons |
| ] |
| if args.output: |
| Path(args.output).expanduser().write_bytes(combined) |
| if args.image_output_dir: |
| image_paths = write_extracted_images( |
| Path(args.image_output_dir).expanduser(), full_images, prefix="full" |
| ) |
| stats["image_output_files"] = image_paths |
| for item in comparisons: |
| report = item.get("image_report") or {} |
| comparison_images = [] |
| for encoded in report.get("images", []): |
| raw = base64.b64decode(str(encoded["data_uri"]).split(",", 1)[1]) |
| comparison_images.append({**encoded, "png": raw}) |
| stats.setdefault("comparison_image_output_files", {})[item["mode"]] = ( |
| write_extracted_images( |
| Path(args.image_output_dir).expanduser(), |
| comparison_images, |
| prefix=str(item["mode"]), |
| ) |
| ) |
| print(json.dumps(stats, indent=2)) |
| if args.stats_json: |
| Path(args.stats_json).expanduser().write_text( |
| json.dumps(stats, indent=2), encoding="utf-8" |
| ) |
| if args.html_output: |
| html_path = Path(args.html_output).expanduser() |
| html_path.parent.mkdir(parents=True, exist_ok=True) |
| html_path.write_text( |
| make_generation_html( |
| prompt, |
| generated, |
| attributions, |
| stats, |
| comparisons=comparisons, |
| image_report=full_image_report, |
| ), |
| encoding="utf-8", |
| ) |
| print(f"wrote hierarchy influence report: {html_path}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|