| """ |
| |
| Generate trajectories using the Dream diffusion language model on GSM8k / competition_math datasets. |
| |
| Core logic: |
| - Each diffusion step decodes only 1 token (steps = max_new_tokens) |
| - If the model's final answer is correct, collect the complete trajectory (token sequence at each step) |
| - Trajectories are written in JSONL format, consistent with trajectory_generation.py |
| |
| Usage example: |
| python dream_gen_math_traj.py \ |
| --model_name Dream-org/Dream-v0-Instruct-7B \ |
| --output_path outputs/gsm8k_dream_traj.jsonl \ |
| --dataset_name gsm8k \ |
| --dataset_config main \ |
| --dataset_split train \ |
| --max_new_tokens 256 \ |
| --num_samples 1 \ |
| --temperature 0.2 \ |
| --top_p 0.95 \ |
| --limit 100 |
| """ |
|
|
| import argparse |
| import json |
| import os |
| import random |
| import re |
| import sys |
|
|
| import numpy as np |
| import torch |
| from datasets import load_dataset |
| from tqdm import tqdm |
| from transformers import AutoModel, AutoTokenizer |
|
|
| |
| |
| _DLLM_EVAL_PATH = os.path.join( |
| os.path.dirname(__file__), |
| "..", |
| "TAD", |
| "eval", |
| ) |
| if os.path.isdir(_DLLM_EVAL_PATH): |
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) |
| from dllm_inference_acceleration_main.eval.parsers import Parser, is_equiv |
| else: |
| import re as _re |
|
|
| def _remove_boxed(s): |
| if "\\boxed " in s: |
| return s[len("\\boxed "):] |
| left = "\\boxed{" |
| try: |
| assert s[: len(left)] == left and s[-1] == "}" |
| return s[len(left): -1] |
| except Exception: |
| return s |
|
|
| def _last_boxed_only_string(string): |
| idx = string.rfind("\\boxed") |
| if "\\boxed " in string: |
| return "\\boxed " + string.split("\\boxed ")[-1].split("$")[0] |
| if idx < 0: |
| idx = string.rfind("\\fbox") |
| if idx < 0: |
| return string |
| i, right_brace_idx, num_open = idx, None, 0 |
| while i < len(string): |
| if string[i] == "{": |
| num_open += 1 |
| if string[i] == "}": |
| num_open -= 1 |
| if num_open == 0: |
| right_brace_idx = i |
| break |
| i += 1 |
| return string[idx: right_brace_idx + 1] if right_brace_idx is not None else None |
|
|
| class Parser: |
| @classmethod |
| def extract_answer_gsm8k(cls, text): |
| try: |
| m = _re.search(r"####\s*\$?([\d,]+(?:\.\d+)?)", text) |
| if m: |
| return float(m.group(1).replace(",", "")) |
| except Exception: |
| pass |
| return None |
|
|
| @classmethod |
| def extract_answer_boxed(cls, text): |
| try: |
| return _remove_boxed(_last_boxed_only_string(text)) |
| except Exception: |
| return text |
|
|
| def _strip_string(s): |
| s = s.replace("\n", "").replace("\\!", "").replace("\\\\", "\\") |
| s = s.replace("tfrac", "frac").replace("dfrac", "frac") |
| s = s.replace("\\left", "").replace("\\right", "") |
| s = s.replace("^{\\circ}", "").replace("^\\circ", "") |
| s = s.replace("\\$", "").replace("\\%", "").replace("\%", "") |
| s = s.replace(" .", " 0.").replace("{.", "{0.") |
| if not s: |
| return s |
| if s[0] == ".": |
| s = "0" + s |
| if len(s.split("=")) == 2 and len(s.split("=")[0]) <= 2: |
| s = s.split("=")[1] |
| s = s.replace(" ", "") |
| return s |
|
|
| def is_equiv(str1, str2, verbose=False): |
| if isinstance(str1, float) or isinstance(str2, float): |
| try: |
| return abs(float(str1) - float(str2)) < 1e-6 |
| except Exception: |
| return False |
| if str1 is None and str2 is None: |
| return True |
| if str1 is None or str2 is None: |
| return False |
| try: |
| return _strip_string(str(str1)) == _strip_string(str(str2)) |
| except Exception: |
| return str1 == str2 |
|
|
|
|
| |
|
|
| def select_device(): |
| if torch.cuda.is_available(): |
| return "cuda" |
| mps_backend = getattr(torch.backends, "mps", None) |
| if mps_backend is not None and mps_backend.is_available(): |
| return "mps" |
| return "cpu" |
|
|
|
|
| def build_prompt(tokenizer, question: str) -> str: |
| messages = [{"role": "user", "content": question}] |
| return tokenizer.apply_chat_template( |
| messages, add_generation_prompt=True, tokenize=False |
| ) |
|
|
|
|
| def extract_last_number(text: str): |
| nums = re.findall(r"-?\d{1,3}(?:,\d{3})+(?:\.\d+)?|-?\d+(?:\.\d+)?", text) |
| return nums[-1] if nums else None |
|
|
|
|
| def extract_pred(text: str): |
| pred = Parser.extract_answer_boxed(text) |
| if pred is None: |
| pred = Parser.extract_answer_gsm8k(text) |
| if pred is None: |
| return extract_last_number(text) |
| pred_num = extract_last_number(pred) |
| return pred_num if pred_num is not None else pred |
|
|
|
|
| def extract_example(example, question_key: str, answer_key: str): |
| """Extract question, answer, and dataset type from a dataset sample.""" |
| if question_key and answer_key: |
| return example[question_key], example[answer_key], "custom" |
| if "question" in example and "answer" in example: |
| return example["question"], example["answer"], "gsm8k" |
| if "problem" in example and "solution" in example: |
| return example["problem"], example["solution"], "math" |
| raise ValueError( |
| "Cannot infer question/answer fields, please specify via --question_key and --answer_key." |
| ) |
|
|
|
|
| |
|
|
| @torch.no_grad() |
| def generate_dream_trajectory( |
| model, |
| tokenizer, |
| input_ids: torch.Tensor, |
| attention_mask: torch.Tensor, |
| max_new_tokens: int, |
| temperature: float, |
| top_p: float, |
| alg: str, |
| alg_temp: float, |
| block_length: int=32, |
| ): |
| """ |
| Call Dream model's diffusion_generate interface with steps = max_new_tokens, |
| i.e., each diffusion step decodes only 1 token. |
| |
| Returns: |
| output_text : str, final generated text (truncated after eos) |
| traj_dict : dict, keys are "step0", "step1", ..., values are token id lists for the generation region |
| (consistent with trajectory_generation.py format) |
| """ |
| output = model.diffusion_generate( |
| input_ids, |
| attention_mask=attention_mask, |
| max_new_tokens=max_new_tokens, |
| output_history=True, |
| return_dict_in_generate=True, |
| steps=max_new_tokens, |
| temperature=temperature, |
| top_p=top_p, |
| alg=alg, |
| alg_temp=alg_temp, |
| block_length=block_length, |
| ) |
|
|
| |
| prompt_len = input_ids.shape[1] |
| final_ids = output.sequences[0] |
| gen_ids = final_ids[prompt_len:].tolist() |
| output_text = tokenizer.decode(gen_ids, skip_special_tokens=False) |
| |
| eos_token = tokenizer.eos_token |
| if eos_token and eos_token in output_text: |
| output_text = output_text.split(eos_token)[0] |
|
|
| |
| |
| traj_dict = {} |
| history = output.history |
| for step_idx, h in enumerate(history): |
| |
| gen_part = h[0, prompt_len:].tolist() |
| traj_dict[f"step{step_idx}"] = gen_part |
|
|
| return output_text, traj_dict |
|
|
|
|
| |
|
|
| def main(): |
| parser = argparse.ArgumentParser( |
| description="Generate math reasoning trajectories using Dream diffusion language model" |
| ) |
| parser.add_argument( |
| "--model_name", |
| type=str, |
| default="", |
| help="Dream model path or HuggingFace Hub ID", |
| ) |
| parser.add_argument("--output_path", type=str, required=True, help="Output JSONL file path") |
| parser.add_argument("--max_new_tokens", type=int, default=256, help="Max generated tokens (also the number of diffusion steps)") |
| parser.add_argument("--block_length", type=int, default=32, help="Block length") |
| parser.add_argument("--num_samples", type=int, default=1, help="Number of samples per problem") |
| parser.add_argument("--temperature", type=float, default=0.2, help="Sampling temperature") |
| parser.add_argument("--top_p", type=float, default=0.95, help="Top-p for nucleus sampling") |
| parser.add_argument("--alg", type=str, default="entropy", help="Dream decoding algorithm (entropy / origin etc.)") |
| parser.add_argument("--alg_temp", type=float, default=0.0, help="Dream decoding algorithm temperature") |
| parser.add_argument("--seed", type=int, default=42, help="Random seed") |
| parser.add_argument("--limit", type=int, default=0, help="Max samples to process, 0 means all") |
| parser.add_argument( |
| "--dataset_name", |
| type=str, |
| default="gsm8k", |
| help="Dataset name", |
| ) |
| parser.add_argument("--dataset_config", type=str, default=None, help="Dataset config") |
| parser.add_argument("--dataset_split", type=str, default="train", help="Dataset split") |
| parser.add_argument("--question_key", type=str, default="", help="Custom question field name") |
| parser.add_argument("--answer_key", type=str, default="", help="Custom answer field name") |
| args = parser.parse_args() |
|
|
| |
| random.seed(args.seed) |
| np.random.seed(args.seed) |
| torch.manual_seed(args.seed) |
|
|
| |
| device = select_device() |
| dtype_map = {"cuda": torch.bfloat16, "mps": torch.float16, "cpu": torch.float32} |
| dtype = dtype_map[device] |
| print(f"[INFO] Device: {device} dtype={dtype}") |
|
|
| print(f"[INFO] Loading model: {args.model_name}") |
| model = AutoModel.from_pretrained( |
| args.model_name, torch_dtype=dtype, trust_remote_code=True |
| ).to(device).eval() |
|
|
| tokenizer = AutoTokenizer.from_pretrained( |
| args.model_name, trust_remote_code=True, padding_side="left" |
| ) |
|
|
| |
| print(f"[INFO] Loading dataset: {args.dataset_name}/{args.dataset_config} split={args.dataset_split}") |
| if args.dataset_config is not None: |
| ds = load_dataset(args.dataset_name, args.dataset_config, split=args.dataset_split) |
| else: |
| ds = load_dataset(args.dataset_name, split=args.dataset_split) |
|
|
| |
| out_dir = os.path.dirname(args.output_path) |
| if out_dir: |
| os.makedirs(out_dir, exist_ok=True) |
| out_f = open(args.output_path, "w", encoding="utf-8") |
|
|
| total = len(ds) if args.limit <= 0 else min(args.limit, len(ds)) |
| solved = 0 |
|
|
| for idx in tqdm(range(total), desc=f"dataset={args.dataset_name}"): |
| raw_q, raw_a, kind = extract_example(ds[idx], args.question_key, args.answer_key) |
|
|
| |
| if kind == "gsm8k": |
| answer_gt = Parser.extract_answer_gsm8k(raw_a) |
| else: |
| |
| answer_gt = Parser.extract_answer_boxed(raw_a) |
|
|
| |
| question = ( |
| f"Question: {raw_q}\n" |
| f"Reference Answer: {raw_a}\n" |
| f"After understanding the reference answer, please try to solve this problem " |
| f"using your own approach below and output a detailed solution process:" |
| ) |
|
|
| |
| prompt_str = build_prompt(tokenizer, question) |
| encoded = tokenizer( |
| [prompt_str], |
| add_special_tokens=False, |
| padding=True, |
| return_tensors="pt", |
| ) |
| input_ids = encoded["input_ids"].to(device) |
| attention_mask = encoded["attention_mask"].to(device) |
|
|
| counted = False |
|
|
| for sample_id in range(args.num_samples): |
| output_text, traj_dict = generate_dream_trajectory( |
| model=model, |
| tokenizer=tokenizer, |
| input_ids=input_ids, |
| attention_mask=attention_mask, |
| max_new_tokens=args.max_new_tokens, |
| temperature=args.temperature, |
| top_p=args.top_p, |
| alg=args.alg, |
| alg_temp=args.alg_temp, |
| block_length=args.block_length, |
| ) |
| print(f"output_text:{output_text}") |
|
|
| pred = extract_pred(output_text) |
| print(f"[idx={idx} sample={sample_id}] pred={pred} gt={answer_gt}") |
|
|
| if is_equiv(pred, answer_gt): |
| if not counted: |
| solved += 1 |
| counted = True |
|
|
| |
| record = { |
| "dataset_name": args.dataset_name, |
| "dataset_config": args.dataset_config, |
| "dataset_split": args.dataset_split, |
| "index": idx, |
| "sample_id": sample_id, |
| "prompt": prompt_str, |
| "question": question, |
| "answer_gt": answer_gt, |
| "pred": pred, |
| "prompt_len": int(input_ids.shape[1]), |
| "trajectory": traj_dict, |
| "output_text": output_text, |
| } |
| out_f.write(json.dumps(record, ensure_ascii=False) + "\n") |
| out_f.flush() |
|
|
| |
| accuracy = solved / total if total > 0 else 0.0 |
| print(f"Accuracy_any_success: {solved}/{total} = {accuracy * 100:.2f}%") |
| out_f.write( |
| json.dumps( |
| { |
| "summary": "any_success", |
| "solved": solved, |
| "total": total, |
| "accuracy": accuracy, |
| }, |
| ensure_ascii=False, |
| ) |
| + "\n" |
| ) |
| out_f.close() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|