Visible generated tokens so far
Previously decoded blocks stay visible and the raw token sequence grows over time. The final generated answer is rendered as markdown only after the full sequence finishes decoding.
import argparse import html import json import re from pathlib import Path import torch from transformers import AutoModelForCausalLM, AutoTokenizer DEFAULT_MODEL_PATH = "Zigeng/DMax-Math-16B" DEFAULT_PROMPT = "Solve 37 * 48 and explain the intermediate reasoning briefly." STAGE_SIDEBAR_WIDTH = 320 STAGE_WIDTH = 720 STAGE_MIN_HEIGHT = 620 STAGE_FINAL_HEIGHT_VH = 72 STAGE_FINAL_MAX_HEIGHT = 860 TOKEN_GRID_MIN_HEIGHT = 600 FINAL_MARKDOWN_BASE_FONT_SIZE = 20 FINAL_MARKDOWN_MIN_FONT_SIZE = 11 PLAYBACK_INTERVAL_MS = 180 def parse_args(): parser = argparse.ArgumentParser( description="Visualize the full step-by-step decoding process of the diffusion LM." ) parser.add_argument("--model-path", default=DEFAULT_MODEL_PATH) parser.add_argument("--device", default="cuda:0") parser.add_argument("--prompt", default=None) parser.add_argument("--gsm8k-index", type=int, default=1) parser.add_argument("--gen-length", type=int, default=512) parser.add_argument("--block-length", type=int, default=32) parser.add_argument("--steps", type=int, default=32) parser.add_argument("--threshold", type=float, default=0.0) parser.add_argument("--output", default="dllm_demo.html") return parser.parse_args() def load_prompt(args): if args.prompt is not None: return args.prompt try: from datasets import load_dataset ds = load_dataset("openai/gsm8k", "main", split="test") return ds[args.gsm8k_index]["question"] + "\nLet's think step by step\n" except Exception: return DEFAULT_PROMPT def tokenize_prompt(tokenizer, prompt): if hasattr(tokenizer, "apply_chat_template"): return tokenizer.apply_chat_template( [{"role": "user", "content": prompt}], add_generation_prompt=True, tokenize=True, return_tensors="pt", ) return tokenizer(prompt, return_tensors="pt").input_ids def format_token_label(tokenizer, token_id, mask_id, eos_id): if token_id == mask_id: return "[MASK]" if eos_id is not None and token_id == eos_id: return "[EOS]" text = tokenizer.decode( [token_id], skip_special_tokens=False, clean_up_tokenization_spaces=False, ) if not text: text = tokenizer.convert_ids_to_tokens(token_id) if text is None: text = str(token_id) text = ( text.replace("\n", "↵") .replace("\t", "⇥") .replace("\r", "␍") .replace(" ", "·") ) if text == "": text = "∅" if len(text) > 20: text = text[:17] + "..." return text def collect_token_labels(tokenizer, demo_trace): used_ids = set(demo_trace["prompt_token_ids"]) used_ids.update(demo_trace["generated_token_ids"]) used_ids.update(demo_trace["final_token_ids"]) for frame in demo_trace["frames"]: used_ids.update(frame.get("visible_generated_ids", [])) used_ids.update(frame.get("pre_visible_ids", [])) used_ids.update(frame.get("post_visible_ids", [])) used_ids.update(frame.get("top1_token_ids", [])) used_ids.update(frame.get("block_top1_token_ids", [])) mask_id = demo_trace["mask_id"] eos_id = demo_trace["eos_id"] token_labels = {} for token_id in sorted(used_ids): token_labels[str(token_id)] = format_token_label( tokenizer, token_id, mask_id, eos_id ) return token_labels def enrich_demo_trace_for_render(demo_trace): prompt_length = demo_trace["prompt_length"] original_frames = demo_trace["frames"] if not original_frames: return demo_trace enriched_frames = [] for frame in original_frames: generated_before_ids = frame["pre_visible_ids"][prompt_length:] generated_after_ids = frame["post_visible_ids"][prompt_length:] current_block_abs_start = max(prompt_length, frame["block_start"]) current_block_generated_start = max(0, current_block_abs_start - prompt_length) prompt_overlap = max(0, prompt_length - frame["block_start"]) visible_generated_absolute_positions = list( range(prompt_length, frame["window_end"]) ) frame["generated_before_ids"] = generated_before_ids frame["generated_after_ids"] = generated_after_ids frame["visible_generated_ids"] = generated_after_ids frame["visible_generated_absolute_positions"] = visible_generated_absolute_positions frame["current_block_absolute_start"] = current_block_abs_start frame["current_block_generated_start"] = current_block_generated_start frame["block_top1_confidence"] = frame["top1_confidence"][prompt_overlap:] frame["block_top1_token_ids"] = frame["top1_token_ids"][prompt_overlap:] frame["block_input_confidence"] = frame["input_confidence"][prompt_overlap:] frame["block_mask_index_before"] = frame["mask_index_before"][prompt_overlap:] frame["block_token_index_before"] = frame["token_index_before"][prompt_overlap:] frame["block_active_mask"] = frame["active_block_mask"][prompt_overlap:] frame["block_absolute_positions"] = list( range(current_block_abs_start, frame["block_end"]) ) frame["block_prompt_overlap"] = prompt_overlap frame["block_decoded_positions"] = [ pos - prompt_overlap for pos in frame["decoded_positions"] if pos >= prompt_overlap ] frame["decoded_absolute_positions"] = [ current_block_abs_start + pos for pos in frame["block_decoded_positions"] ] frame["state_mode"] = "after_step" enriched_frames.append(frame) first_frame = enriched_frames[0] initial_frame = { "frame_id": -1, "block_id": first_frame["block_id"], "absolute_block_id": first_frame["absolute_block_id"], "step_id": -1, "window_end": first_frame["window_end"], "block_start": first_frame["block_start"], "block_end": first_frame["block_end"], "nfe": 0, "visible_generated_ids": first_frame["generated_before_ids"], "visible_generated_absolute_positions": list( range(prompt_length, first_frame["window_end"]) ), "current_block_absolute_start": first_frame["current_block_absolute_start"], "current_block_generated_start": first_frame["current_block_generated_start"], "block_top1_confidence": [], "block_top1_token_ids": [], "block_input_confidence": first_frame["block_input_confidence"], "block_mask_index_before": first_frame["block_mask_index_before"], "block_token_index_before": first_frame["block_token_index_before"], "block_active_mask": first_frame["block_active_mask"], "block_absolute_positions": first_frame["block_absolute_positions"], "block_prompt_overlap": first_frame["block_prompt_overlap"], "block_decoded_positions": [], "decoded_absolute_positions": [], "same_as_previous": False, "all_confident": False, "converged": False, "convergence_reason": None, "state_mode": "before_first_step", } demo_trace["frames"] = [initial_frame] for idx, frame in enumerate(enriched_frames, start=1): frame["frame_id"] = idx demo_trace["frames"].append(frame) return demo_trace def markdown_to_html(text): escaped = html.escape(text.replace("\r\n", "\n").replace("\r", "\n")) code_blocks = [] def stash_code(match): code = match.group(1).strip("\n") placeholder = f"@@CODEBLOCK{len(code_blocks)}@@" code_blocks.append(f"
{code}")
return placeholder
escaped = re.sub(r"```(?:[^\n`]*)\n(.*?)```", stash_code, escaped, flags=re.S)
def format_inline(content):
content = re.sub(r"`([^`]+)`", r"\1", content)
content = re.sub(r"\*\*([^*]+)\*\*", r"\1", content)
content = re.sub(r"\*([^*]+)\*", r"\1", content)
return content
parts = []
paragraph_lines = []
in_ul = False
in_ol = False
def close_lists():
nonlocal in_ul, in_ol
if in_ul:
parts.append("")
in_ul = False
if in_ol:
parts.append("")
in_ol = False
def flush_paragraph():
nonlocal paragraph_lines
if paragraph_lines:
merged_parts = []
non_empty_lines = [line for line in paragraph_lines if line.strip()]
for idx, line in enumerate(non_empty_lines):
hard_break = line.endswith(" ") or line.endswith("\\")
content = line[:-1] if line.endswith("\\") else line
merged_parts.append(format_inline(content.strip()))
if idx < len(non_empty_lines) - 1:
merged_parts.append("{merged}
") paragraph_lines = [] for raw_line in escaped.split("\n"): line = raw_line stripped = line.strip() if not stripped: flush_paragraph() close_lists() continue heading = re.match(r"^(#{1,6})\s+(.*)$", stripped) unordered = re.match(r"^[-*+]\s+(.*)$", stripped) ordered = re.match(r"^(\d+)\.\s+(.*)$", stripped) code_block = re.fullmatch(r"@@CODEBLOCK(\d+)@@", stripped) if code_block: flush_paragraph() close_lists() parts.append(stripped) elif heading: flush_paragraph() close_lists() level = len(heading.group(1)) parts.append(f"Diffusion Language Model
Previously decoded blocks stay visible and the raw token sequence grows over time. The final generated answer is rendered as markdown only after the full sequence finishes decoding.