| from __future__ import annotations |
| import json, os |
| from dataclasses import dataclass |
| from typing import List |
| import random |
| from jinja2 import Template |
| import torch |
| from termcolor import cprint |
| import torch.nn.functional as F |
| from transformers import AutoTokenizer |
| from llava.model.builder import load_pretrained_model |
| from llava.mm_utils import process_images, tokenizer_image_token |
| from llava.constants import IMAGE_TOKEN_INDEX, DEFAULT_IMAGE_TOKEN, DEFAULT_IM_START_TOKEN, DEFAULT_IM_END_TOKEN |
| from llava.conversation import conv_templates |
| import multiprocessing as mp |
| from tqdm import tqdm |
| import re |
|
|
| from omegaconf import OmegaConf |
| def get_config(): |
| cli_conf = OmegaConf.from_cli() |
| yaml_conf = OmegaConf.load(cli_conf.config) |
| conf = OmegaConf.merge(yaml_conf, cli_conf) |
| return conf |
|
|
| def add_gumbel_noise(logits, temperature): |
| if temperature == 0: |
| return logits |
| logits = logits.to(torch.float64) |
| noise = torch.rand_like(logits, dtype=torch.float64) |
| noise = (- torch.log(noise)) ** temperature |
| return logits.exp() / noise |
|
|
| def get_num_transfer_tokens(mask_index, steps): |
| mask_num = mask_index.sum(dim=1, keepdim=True) |
| base = mask_num // steps |
| remainder = mask_num % steps |
| num_transfer_tokens = torch.zeros(mask_num.size(0), steps, device=mask_index.device, dtype=torch.int64) + base |
| for i in range(mask_num.size(0)): |
| num_transfer_tokens[i, :remainder[i]] += 1 |
| return num_transfer_tokens |
|
|
| @dataclass |
| class DiffusionOutput: |
| sequences: torch.Tensor |
| history: List[torch.Tensor] |
| nfe: int |
| L0: int |
|
|
| def build_llava_prompt(question: str, conv_template: str = "llava_llada"): |
| import copy |
| conv = copy.deepcopy(conv_templates[conv_template]) |
| conv.append_message(conv.roles[0], DEFAULT_IMAGE_TOKEN + "\n" + question) |
| conv.append_message(conv.roles[1], None) |
| return conv.get_prompt() |
|
|
| @torch.no_grad() |
| def generate_llada_v_with_history( |
| model, tokenizer, input_ids, images, image_sizes, |
| steps, gen_length, block_length, temperature, |
| target, unmask_threshold=None, mask_id=126336 |
| ) -> DiffusionOutput: |
| position_ids = None |
| attention_mask = None |
| inputs_embeds = None |
| (_input_ids, position_ids, attention_mask, _pkv, inputs_embeds, _labels) = model.prepare_inputs_labels_for_multimodal( |
| input_ids, position_ids, attention_mask, None, None, images, ["image"], image_sizes=image_sizes |
| ) |
| device = inputs_embeds.device |
| B = inputs_embeds.shape[0] |
| L0 = inputs_embeds.shape[1] |
| d = inputs_embeds.shape[2] |
| assert B == 1 |
| total_len = L0 + gen_length |
| masked_embed = model.get_model().embed_tokens(torch.tensor([mask_id], device=device)) |
| x_embeds = masked_embed.repeat(B, total_len, 1) |
| x_embeds[:, :L0] = inputs_embeds |
| x_tokens = torch.full((B, total_len), mask_id, dtype=torch.long, device=device) |
| assert gen_length % block_length == 0 |
| num_blocks = gen_length // block_length |
| base, rem = divmod(steps, num_blocks) |
| steps_per_block = [base + (i < rem) for i in range(num_blocks)] |
| nfe = 0 |
| hist: List[torch.Tensor] = [] |
| for blk in range(num_blocks): |
| s = L0 + blk * block_length |
| e = L0 + (blk + 1) * block_length |
| cur_steps = steps_per_block[blk] |
| outputs = model.get_model()(inputs_embeds=x_embeds, attention_mask=None, position_ids=None, use_cache=False, return_dict=True) |
| logits = model.lm_head(outputs.last_hidden_state).float() |
| blk_tokens = x_tokens[:, s:e] |
| eos_mask = (blk_tokens == 126348) |
| if eos_mask.any(): |
| first_eos_pos = torch.where(eos_mask[0])[0][0].item() |
| valid_region = torch.zeros_like(blk_tokens, dtype=torch.bool) |
| valid_region[:, :first_eos_pos] = True |
| else: |
| valid_region = torch.ones_like(blk_tokens, dtype=torch.bool) |
| mask_blk = (blk_tokens == mask_id) & valid_region |
| num_transfer = get_num_transfer_tokens(mask_blk, cur_steps) |
| x0_blk, tr_idx_blk = get_transfer_index( |
| logits[:, s:e], temperature, target, |
| mask_blk, blk_tokens, num_transfer[:, 0], |
| threshold=unmask_threshold |
| ) |
| x_slice = x_tokens[:, s:e] |
| x_slice[tr_idx_blk] = x0_blk[tr_idx_blk] |
| x_tokens[:, s:e] = x_slice |
| x_embeds[:, s:e] = model.get_model().embed_tokens(x_tokens[:, s:e]) |
| hist.append(x_tokens.clone().cpu()) |
| nfe += 1 |
| i = 1 |
| while i < cur_steps: |
| nfe += 1 |
| outputs = model.get_model()(inputs_embeds=x_embeds, attention_mask=None, position_ids=None, use_cache=False, return_dict=True) |
| logits = model.lm_head(outputs.last_hidden_state).float() |
| blk_tokens = x_tokens[:, s:e] |
| eos_mask = (blk_tokens == 126348) |
| if eos_mask.any(): |
| first_eos_pos = torch.where(eos_mask[0])[0][0].item() |
| valid_region = torch.zeros_like(blk_tokens, dtype=torch.bool) |
| valid_region[:, :first_eos_pos] = True |
| else: |
| valid_region = torch.ones_like(blk_tokens, dtype=torch.bool) |
| mask_blk = (x_tokens[:, s:] == mask_id) |
| mask_blk[:, block_length:] = False |
| mask_blk[:, :block_length] = mask_blk[:, :block_length] & valid_region |
| x0, tr_idx = get_transfer_index( |
| logits[:, s:], temperature, target, |
| mask_blk, x_tokens[:, s:], num_transfer[:, i], |
| threshold=unmask_threshold |
| ) |
| if tr_idx.any(): |
| x_slice = x_tokens[:, s:] |
| x_slice[tr_idx] = x0[tr_idx] |
| x_tokens[:, s:] = x_slice |
| x0_embeds = model.get_model().embed_tokens(torch.where(tr_idx, x0, x_slice)) |
| x_embeds[:, s:][tr_idx] = x0_embeds[tr_idx] |
| hist.append(x_tokens.clone().cpu()) |
| remaining_masks = (x_tokens[:, s:e] == mask_id) & valid_region |
| if remaining_masks.sum() == 0: |
| break |
| i += 1 |
| if (x_tokens[:, s:e] == 126348).any(): |
| break |
| return DiffusionOutput(sequences=x_tokens, history=hist, nfe=nfe, L0=L0) |
|
|
| def get_transfer_index(logits, temperature, target, mask_index, x, num_transfer_tokens, threshold=None): |
| logits_with_noise = add_gumbel_noise(logits, temperature=temperature) |
| logits_with_noise[..., 126336] = -float('inf') |
| x0 = torch.argmax(logits_with_noise, dim=-1) |
| if target == 'confidence': |
| p = F.softmax(logits.to(torch.float64), dim=-1) |
| x0_p = torch.squeeze( |
| torch.gather(p, dim=-1, index=torch.unsqueeze(x0, -1)), -1) |
| elif target == 'margin_confidence': |
| p = F.softmax(logits.to(torch.float64), dim=-1) |
| top2 = torch.topk(p, 2, dim=-1).values |
| x0_p = top2[..., 0] - top2[..., 1] |
| elif target == 'neg_entropy': |
| p = F.softmax(logits.to(torch.float64), dim=-1) |
| x0_p = -torch.sum(p * torch.log(p + 1e-10), dim=-1) |
| elif target == 'random': |
| x0_p = torch.rand((x0.shape[0], x0.shape[1]), device=x0.device) |
| else: |
| raise NotImplementedError(target) |
| x0 = torch.where(mask_index, x0, x) |
| if threshold is not None: |
| selected = mask_index & (x0_p >= threshold) |
| for j in range(selected.shape[0]): |
| k = int(num_transfer_tokens[j].item() if torch.is_tensor(num_transfer_tokens[j]) else num_transfer_tokens[j]) |
| if k <= 0: |
| continue |
| num_selected = selected[j].sum().item() |
| if num_selected < k: |
| masked_scores = x0_p[j].masked_fill(~mask_index[j], float("-inf")) |
| _, top_indices = torch.topk(masked_scores, k=k) |
| selected[j, :] = False |
| selected[j, top_indices] = True |
| return x0, selected |
| confidence = x0_p.masked_fill(~mask_index, float("-inf")) |
| transfer_index = torch.zeros_like(x0, dtype=torch.bool, device=x0.device) |
| for j in range(confidence.shape[0]): |
| k = int(num_transfer_tokens[j].item() if torch.is_tensor(num_transfer_tokens[j]) else num_transfer_tokens[j]) |
| if k <= 0: |
| continue |
| _, sel = torch.topk(confidence[j], k=k) |
| transfer_index[j, sel] = True |
| return x0, transfer_index |
|
|
| def random_select(data_list, random_k): |
| data_list = random.sample(data_list, random_k) |
| return data_list |
|
|
| def get_prompt(data_i): |
| return Template(system_prompts).render(problem=data_i["question"]) |
|
|
| def extract_final_boxed_answer(s: str): |
| tag = r'\boxed{' |
| start = s.rfind(tag) |
| if start == -1: |
| return "Can not extract the answer!" |
| i = start + len(tag) |
| depth = 1 |
| buf = [] |
| while i < len(s) and depth: |
| ch = s[i] |
| if ch == '{': |
| depth += 1 |
| elif ch == '}': |
| depth -= 1 |
| if depth == 0: |
| break |
| buf.append(ch) |
| i += 1 |
| return ''.join(buf) if depth == 0 else "Can not extract the answer!" |
|
|
| def denoise_step_map(history, mask_id: int, sample_idx: int = 0): |
| L = history[0].shape[1] |
| step_map = torch.zeros(L, dtype=torch.long) |
| prev = torch.full((L,), mask_id, dtype=torch.long) |
| for t, snap in enumerate(history, start=1): |
| cur = snap[sample_idx] |
| changed = (prev == mask_id) & (cur != mask_id) |
| step_map[changed] = t |
| prev = cur |
| unprocessed_mask = (step_map == 0) |
| if unprocessed_mask.any(): |
| max_step = step_map.max() |
| step_map[unprocessed_mask] = max_step |
| return step_map |
|
|
| def worker(pretrained_model, rank, prompts, orig_idx, data_idx, image_paths, seq_dict, step_dict, imgabs_dict, batch_size, config): |
| from PIL import Image |
| import copy |
| torch.cuda.set_device(rank) |
| device = torch.device(f"cuda:{rank}") |
| tokenizer_gpu, model_gpu, image_processor, max_length = load_pretrained_model( |
| pretrained_model, None, "llava_llada", attn_implementation="sdpa", device_map=device |
| ) |
| model_gpu.eval() |
| special_tokens = { |
| "additional_special_tokens": [DEFAULT_IMAGE_TOKEN, DEFAULT_IM_START_TOKEN, DEFAULT_IM_END_TOKEN] |
| } |
| num_new = tokenizer_gpu.add_special_tokens(special_tokens) |
| if num_new > 0: |
| try: |
| model_gpu.resize_token_embeddings(len(tokenizer_gpu), mean_resizing=False) |
| except TypeError: |
| model_gpu.resize_token_embeddings(len(tokenizer_gpu)) |
| mask_id = 126336 |
| unmask_threshold = None if config.rollout.remasking_strategy == "low_confidence_static" else config.rollout.dynamic_threshold |
| for j in tqdm(range(len(prompts)), desc=f"GPU {rank}", position=rank, leave=True): |
| did = data_idx[j] |
| prompt_text = prompts[j] |
| img_path = image_paths[j] if image_paths is not None else None |
| if img_path is not None: |
| if not os.path.isabs(img_path) and hasattr(config.dataset, "image_root") and config.dataset.image_root is not None: |
| img_path = os.path.join(config.dataset.image_root, img_path) |
| img_path = os.path.abspath(img_path) |
| imgabs_dict[did] = img_path |
| image = Image.open(img_path).convert("RGB") |
| image_tensor = process_images([image], image_processor, model_gpu.config) |
| image_tensor = [_image.to(dtype=torch.float16, device=device) for _image in image_tensor] |
| image_sizes = [image.size] |
| else: |
| image_tensor = None |
| image_sizes = None |
| conv_prompt = build_llava_prompt(prompt_text, conv_template="llava_llada") |
| input_ids = tokenizer_image_token(conv_prompt, tokenizer_gpu, IMAGE_TOKEN_INDEX, return_tensors="pt").unsqueeze(0).to(device) |
| out = generate_llada_v_with_history( |
| model=model_gpu, |
| tokenizer=tokenizer_gpu, |
| input_ids=input_ids, |
| images=image_tensor, |
| image_sizes=image_sizes, |
| steps=config.rollout.steps, |
| gen_length=config.rollout.max_gen_length, |
| block_length=config.rollout.block_size, |
| temperature=config.rollout.temperature, |
| target=config.rollout.target, |
| unmask_threshold=unmask_threshold, |
| mask_id=mask_id, |
| ) |
| out.sequences = out.sequences.cpu() |
| L0 = model_gpu.prepare_inputs_labels_for_multimodal(input_ids, None, None, None, None, image_tensor, ["image"], image_sizes=image_sizes)[4].shape[1] |
| seq_ids = out.sequences[:, L0:].tolist() |
| text = tokenizer_gpu.batch_decode(seq_ids, skip_special_tokens=False, clean_up_tokenization_spaces=True)[0] |
| m = denoise_step_map(out.history, mask_id=mask_id, sample_idx=0) |
| step_map = m[L0:].tolist() |
| seq_dict[orig_idx[j]] = text |
| step_dict[orig_idx[j]] = step_map |
| torch.cuda.empty_cache() |
|
|
| def get_data_chunk(data, num_node, node_idx): |
| total = len(data) |
| chunk_size = (total + num_node - 1) // num_node |
| start_idx = node_idx * chunk_size |
| end_idx = min((node_idx + 1) * chunk_size, total) |
| return data[start_idx:end_idx] |
|
|
| def extract_code(full_output): |
| matches = re.findall(r"```python(.*?)```", full_output, re.DOTALL) |
| if matches: |
| code_output = matches[-1].strip() |
| else: |
| code_output = "We can not extract the code in the output. " |
| return code_output |
|
|
| if __name__ == "__main__": |
| config = get_config() |
| mp.set_start_method("spawn", force=True) |
| k_sample = config.rollout.num_response_per_task |
| batch_size = config.rollout.batch_size |
| project_name = config.experiment.project |
| if config.answer_must_in_box: |
| system_prompts = """<|startoftext|><|start_header_id|>user<|end_header_id|>You need to put your final answer in \\boxed{}. This is the problem:\n{{problem}}<|eot_id|><|startoftext|><|start_header_id|>assistant<|end_header_id|>\n""" |
| else: |
| system_prompts = """<|startoftext|><|start_header_id|>user<|end_header_id|>{{problem}}<|eot_id|><|startoftext|><|start_header_id|>assistant<|end_header_id|>\n""" |
| dataset = config.dataset.eval_dataset |
| pretrained_model = config.model |
| with open("../data/" + dataset + ".json", 'r') as f: |
| data = json.load(f) |
| num_node = config.experiment.num_node |
| node_index = config.experiment.node_index |
| if num_node > 1: |
| data = get_data_chunk(data, num_node, node_index) |
| num = len(data) |
| tokenizer = AutoTokenizer.from_pretrained(pretrained_model, trust_remote_code=True) |
| generation_prompts = [] |
| prefix_list = [] |
| index_list = [] |
| for i in range(num): |
| generation_prompts = generation_prompts + [get_prompt(data[i])] * k_sample |
| index_list = index_list + [i] * k_sample |
| data[i]["full_output"] = [] |
| data[i]["step_map"] = [] |
| data[i]["extracted_output"] = [] |
| data[i]["response_length"] = [] |
| data[i]["prompt"] = get_prompt(data[i]) |
| image_paths = [] |
| for i in range(num): |
| image_paths += [data[i].get("image") or data[i].get("image_path")] * k_sample |
| cprint("start generation...", "green") |
| all_prompts = generation_prompts |
| N = len(all_prompts) |
| shuffled_idx = list(range(N)) |
| random.shuffle(shuffled_idx) |
| shuffled_prompts = [all_prompts[i] for i in shuffled_idx] |
| def split_even(lst, n): |
| k, m = divmod(len(lst), n) |
| return [lst[i*k+min(i,m):(i+1)*k+min(i+1,m)] for i in range(n)] |
| n_gpu = torch.cuda.device_count() |
| assert n_gpu > 1 |
| prompt_chunks = split_even(shuffled_prompts, n_gpu) |
| idx_chunks = split_even(shuffled_idx, n_gpu) |
| data_idx_full = [index_list[i] for i in shuffled_idx] |
| data_idx_chunks = split_even(data_idx_full, n_gpu) |
| image_paths_full = [image_paths[i] for i in shuffled_idx] |
| image_chunks = split_even(image_paths_full, n_gpu) |
| manager = mp.Manager() |
| seq_dict = manager.dict() |
| step_dict = manager.dict() |
| imgabs_dict = manager.dict() |
| procs = [] |
| for rk in range(n_gpu): |
| p = mp.Process(target=worker, |
| args=(pretrained_model, rk, |
| prompt_chunks[rk], |
| idx_chunks[rk], |
| data_idx_chunks[rk], |
| image_chunks[rk], |
| seq_dict, |
| step_dict, |
| imgabs_dict, |
| batch_size, |
| config)) |
| p.start() |
| procs.append(p) |
| for p in procs: |
| p.join() |
| restored_outputs = [seq_dict[i] for i in range(N)] |
| restored_step_maps = [step_dict[i] for i in range(N)] |
| for di in range(num): |
| if di in imgabs_dict: |
| data[di]["image_abs_path"] = imgabs_dict[di] |
| def get_token_lengths(strings, tokenizer): |
| pad_token = "<|mdm_mask|>" |
| escaped = re.escape(pad_token) |
| pattern = rf"(?:{escaped})+" |
| remove_pattern = escaped |
| collapse_re = re.compile(pattern) |
| lengths = [] |
| for s in strings: |
| s_clean = collapse_re.sub(lambda _: pad_token if isinstance(pad_token, str) else '', s) |
| s_clean = re.sub(remove_pattern, '', s_clean) |
| lengths.append(len(tokenizer.encode(s_clean, add_special_tokens=False))) |
| return lengths |
| response_length = get_token_lengths(restored_outputs, tokenizer) |
| i = 0 |
| for full_output in restored_outputs: |
| if config.answer_must_in_box: |
| extracted_output = extract_final_boxed_answer(full_output) |
| else: |
| if "</think>" in full_output: |
| extracted_output = full_output.split("</think>")[1] |
| else: |
| extracted_output = full_output |
| index_i = index_list[i] |
| data[index_i]["full_output"].append(full_output) |
| data[index_i]["step_map"].append(restored_step_maps[i]) |
| data[index_i]["extracted_output"].append(extracted_output) |
| data[index_i]["response_length"].append(response_length[i]) |
| i += 1 |
| outputs_name = "eval-" + pretrained_model.replace("/", ".") + "-" + dataset |
| outputs_name = outputs_name + "-" + config.rollout.remasking_strategy |
| if num_node > 1: |
| output_file_name = "../" + project_name + f"/temp_data/outputs-{node_index}-" + outputs_name + ".json" |
| else: |
| output_file_name = "../" + project_name + "/temp_data/outputs-" + outputs_name + ".json" |
| os.makedirs(os.path.dirname(output_file_name), exist_ok=True) |
| with open(output_file_name, "w", encoding="utf-8") as f: |
| json.dump(data, f, indent=2, ensure_ascii=False) |
|
|