| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import torch |
| import numpy as np |
| import torch.nn.functional as F |
| import os |
| from transformers import AutoTokenizer, AutoModel |
| from model.modeling_llada import LLaDAModelLM |
|
|
| from torch.cuda import nvtx |
|
|
| def add_gumbel_noise(logits, temperature): |
| ''' |
| The Gumbel max is a method for sampling categorical distributions. |
| According to arXiv:2409.02908, for MDM, low-precision Gumbel Max improves perplexity score but reduces generation quality. |
| Thus, we use float64. |
| ''' |
| if temperature == 0: |
| return logits |
| logits = logits.to(torch.float64) |
| noise = torch.rand_like(logits, dtype=torch.float64) |
| gumbel_noise = (- torch.log(noise)) ** temperature |
| return logits.exp() / gumbel_noise |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
|
|
| |
| |
|
|
| |
|
|
| def get_num_transfer_tokens(block_mask_index: torch.Tensor, steps: int) -> torch.Tensor: |
| """ |
| block_mask_index: (B, L) bool – which positions are masked in the current block |
| returns: (B, steps) int – how many tokens to transfer at each step per batch item |
| """ |
| device = block_mask_index.device |
| dtype = torch.long |
|
|
| total = block_mask_index.sum(dim=1) |
| base = torch.div(total, steps, rounding_mode='floor') |
| rem = total - base * steps |
|
|
| |
| num_transfer_tokens = base.unsqueeze(1).expand(-1, steps).to(dtype) |
|
|
| |
| cols = torch.arange(steps, device=device).unsqueeze(0) |
| add_mask = cols < rem.unsqueeze(1) |
| num_transfer_tokens = num_transfer_tokens + add_mask.to(dtype) |
|
|
| return num_transfer_tokens |
|
|
|
|
|
|
| @ torch.no_grad() |
| def generate(model, prompt, steps=128, gen_length=128, block_length=128, temperature=0., |
| remasking='low_confidence', mask_id=126336, threshold=None, factor=None): |
| ''' |
| Args: |
| model: Mask predictor. |
| prompt: A tensor of shape (1, L). |
| steps: Sampling steps, less than or equal to gen_length. |
| gen_length: Generated answer length. |
| block_length: Block length, less than or equal to gen_length. If less than gen_length, it means using semi_autoregressive remasking. |
| temperature: Categorical distribution sampling temperature. |
| cfg_scale: Unsupervised classifier-free guidance scale. |
| remasking: Remasking strategy. 'low_confidence' or 'random'. |
| mask_id: The toke id of [MASK] is 126336. |
| ''' |
| x = torch.full((prompt.shape[0], prompt.shape[1] + gen_length), mask_id, dtype=torch.long).to(model.device) |
| x[:, :prompt.shape[1]] = prompt.clone() |
|
|
| assert gen_length % block_length == 0 |
| num_blocks = gen_length // block_length |
|
|
| assert steps % num_blocks == 0 |
| steps = steps // num_blocks |
|
|
| nfe = 0 |
| for num_block in range(num_blocks): |
| block_mask_index = (x[:, prompt.shape[1] + num_block * block_length: prompt.shape[1] + (num_block + 1) * block_length] == mask_id) |
| num_transfer_tokens = get_num_transfer_tokens(block_mask_index, steps) |
| i = 0 |
| while True: |
| nfe += 1 |
| mask_index = (x == mask_id) |
| logits = model(x).logits |
| mask_index[:, prompt.shape[1] + (num_block + 1) * block_length:] = 0 |
| if factor is None: |
| x0, transfer_index = get_transfer_index(logits, temperature, remasking, mask_index, x, num_transfer_tokens[:, i] if threshold is None else None, threshold) |
| else: |
| x0, transfer_index = get_transfer_index_dynamic(logits, temperature, remasking, mask_index, x, None, factor) |
| x[transfer_index] = x0[transfer_index] |
| i += 1 |
| if (x[:, prompt.shape[1] + num_block * block_length: prompt.shape[1] + (num_block + 1) * block_length] == mask_id).sum() == 0: |
| break |
| return x, nfe |
|
|
|
|
|
|
| @ torch.no_grad() |
| def generate_with_prefix_cache(model, prompt, steps=128, gen_length=128, block_length=128, temperature=0., |
| remasking='low_confidence', mask_id=126336, threshold=None, factor=None): |
| ''' |
| Args: |
| model: Mask predictor. |
| prompt: A tensor of shape (1, L). |
| steps: Sampling steps, less than or equal to gen_length. |
| gen_length: Generated answer length. |
| block_length: Block length, less than or equal to gen_length. If less than gen_length, it means using semi_autoregressive remasking. |
| temperature: Categorical distribution sampling temperature. |
| cfg_scale: Unsupervised classifier-free guidance scale. |
| remasking: Remasking strategy. 'low_confidence' or 'random'. |
| mask_id: The toke id of [MASK] is 126336. |
| ''' |
| x = torch.full((prompt.shape[0], prompt.shape[1] + gen_length), mask_id, dtype=torch.long).to(model.device) |
| x[:, :prompt.shape[1]] = prompt.clone() |
|
|
| assert gen_length % block_length == 0 |
| num_blocks = gen_length // block_length |
|
|
| assert steps % num_blocks == 0 |
| steps = steps // num_blocks |
|
|
| nfe = 0 |
| |
| for num_block in range(num_blocks): |
| current_block_start = prompt.shape[1] + num_block * block_length |
| current_block_end = current_block_start + block_length |
|
|
| block_mask_index = (x[:, current_block_start:current_block_end] == mask_id) |
| num_transfer_tokens = get_num_transfer_tokens(block_mask_index, steps) |
|
|
| output = model(x, use_cache=True) |
| past_key_values = output.past_key_values |
|
|
| mask_index = (x == mask_id) |
| mask_index[:, current_block_end:] = 0 |
| if factor is None: |
| x0, transfer_index = get_transfer_index(output.logits, temperature, remasking, mask_index, x, num_transfer_tokens[:, 0] if threshold is None else None, threshold) |
| else: |
| x0, transfer_index = get_transfer_index_dynamic(output.logits, temperature, remasking, mask_index, x, None, factor) |
| x[transfer_index] = x0[transfer_index] |
|
|
| new_past_key_values = [] |
| for i in range(len(past_key_values)): |
| new_past_key_values.append(()) |
| for j in range(len(past_key_values[i])): |
| new_past_key_values[i] += (past_key_values[i][j][:, :, :current_block_start],) |
| |
| past_key_values = new_past_key_values |
| nfe += 1 |
| |
| i = 1 |
| while True: |
| if (x[:, current_block_start:current_block_end] == mask_id).sum() == 0: |
| break |
| nfe += 1 |
| mask_index = (x[:, current_block_start:] == mask_id) |
| mask_index[:, block_length:] = 0 |
|
|
| logits = model(x[:, current_block_start:], past_key_values=past_key_values, use_cache=True).logits |
|
|
| logits_with_noise = add_gumbel_noise(logits, temperature=temperature) |
| x0 = torch.argmax(logits_with_noise, dim=-1) |
|
|
| if factor is None: |
| x0, transfer_index = get_transfer_index(logits, temperature, remasking, mask_index, |
| x[:, current_block_start:], num_transfer_tokens[:, i] if threshold is None else None, threshold) |
| else: |
| x0, transfer_index = get_transfer_index_dynamic(logits, temperature, remasking, mask_index, |
| x[:, current_block_start:], None, factor) |
| x[:, current_block_start:][transfer_index] = x0[transfer_index] |
| |
| i += 1 |
|
|
|
|
| return x, nfe |
|
|
| @torch.no_grad() |
| def generate_with_dual_cache( |
| model, prompt, steps=128, gen_length=128, block_length=128, temperature=0., |
| remasking="low_confidence", mask_id=126336, threshold=None, factor=None |
| ): |
| B = prompt.shape[0] |
| Lp = int(prompt.shape[1]) |
| assert gen_length % block_length == 0 |
| num_blocks = gen_length // block_length |
|
|
| assert steps % num_blocks == 0 |
| steps_per_block = steps // num_blocks |
|
|
| |
| x = torch.full((B, Lp + gen_length), mask_id, dtype=torch.long, device=model.device) |
| x[:, :Lp] = prompt |
|
|
| nfe = 0 |
|
|
| for nb in range(num_blocks): |
| s = Lp + nb * block_length |
| e = s + block_length |
|
|
| |
| block_mask_index = (x[:, s:e] == mask_id) |
| num_transfer_tokens = get_num_transfer_tokens(block_mask_index, steps_per_block) |
|
|
| |
| out_full = model(x, use_cache=True) |
| past_key_values = out_full.past_key_values |
| nfe += 1 |
|
|
| |
| replace_position = torch.zeros_like(x, dtype=torch.bool) |
| replace_position[:, s:e] = True |
|
|
| |
| global_mask_index = (x == mask_id) |
| |
| global_mask_index[:, e:] = False |
|
|
| if factor is None: |
| quota0 = None if threshold is not None else num_transfer_tokens[:, 0] |
| x0, transfer_index = get_transfer_index( |
| out_full.logits, temperature, remasking, global_mask_index, x, quota0, threshold |
| ) |
| else: |
| x0, transfer_index = get_transfer_index_dynamic( |
| out_full.logits, temperature, remasking, global_mask_index, x, None, factor |
| ) |
|
|
| |
| x = torch.where(transfer_index, x0, x) |
|
|
| |
| |
| for i in range(1, steps_per_block): |
| |
| if (x[:, s:e] == mask_id).sum() == 0: |
| break |
| logits_blk = model( |
| x[:, s:e], past_key_values=past_key_values, use_cache=True, replace_position=replace_position |
| ).logits |
|
|
| |
| mask_blk = (x[:, s:e] == mask_id) |
|
|
| if factor is None: |
| quota_i = None if threshold is not None else num_transfer_tokens[:, i] |
| x0_blk, transfer_idx_blk = get_transfer_index( |
| logits_blk, temperature, remasking, mask_blk, x[:, s:e], quota_i, threshold |
| ) |
| else: |
| x0_blk, transfer_idx_blk = get_transfer_index_dynamic( |
| logits_blk, temperature, remasking, mask_blk, x[:, s:e], None, factor |
| ) |
|
|
| |
| blk_old = x[:, s:e] |
| blk_new = torch.where(transfer_idx_blk, x0_blk, blk_old) |
| x = torch.cat([x[:, :s], blk_new, x[:, e:]], dim=1) |
|
|
| nfe += 1 |
|
|
| return x, nfe |
|
|
|
|
|
|
| def get_transfer_index( |
| logits: torch.Tensor, |
| temperature: float, |
| remasking: str, |
| mask_index: torch.Tensor, |
| x: torch.Tensor, |
| num_transfer_tokens, |
| threshold: float = None, |
| ): |
| """ |
| Returns: |
| x0: (B, L) long — proposed tokens |
| transfer_index: (B, L) bool — which positions to update this step |
| """ |
| |
| |
| logits_with_noise = add_gumbel_noise(logits, temperature=temperature) |
| x0 = torch.argmax(logits_with_noise, dim=-1) |
|
|
| |
| if remasking == "low_confidence": |
| |
| p = F.softmax(logits.to(torch.float64), dim=-1) |
| x0_p = torch.gather(p, dim=-1, index=x0.unsqueeze(-1)).squeeze(-1) |
| elif remasking == "random": |
| x0_p = torch.rand(x0.shape, device=x0.device, dtype=torch.float64) |
| else: |
| raise NotImplementedError(remasking) |
|
|
| |
| x0 = torch.where(mask_index, x0, x) |
|
|
| neg_inf = torch.tensor(torch.finfo(x0_p.dtype).min, device=x0_p.device, dtype=x0_p.dtype) |
| confidence = torch.where(mask_index, x0_p, neg_inf) |
|
|
| |
| if threshold is not None: |
| |
| |
| transfer_index = mask_index & (confidence >= threshold) |
|
|
| |
| max_conf_indices = torch.argmax(confidence, dim=1, keepdim=True) |
| force_mask = torch.zeros_like(transfer_index).scatter_(1, max_conf_indices, True) |
|
|
| |
| transfer_index = transfer_index | force_mask |
|
|
| |
| transfer_index = transfer_index & mask_index |
|
|
| return x0, transfer_index |
|
|
| |
| if num_transfer_tokens is None: |
| raise ValueError("num_transfer_tokens must be a tensor when threshold is None.") |
|
|
| |
| if num_transfer_tokens.dim() == 2 and num_transfer_tokens.size(1) == 1: |
| num_transfer_tokens = num_transfer_tokens.squeeze(1) |
| num_transfer_tokens = num_transfer_tokens.to(dtype=torch.long, device=confidence.device) |
| num_transfer_tokens = torch.clamp(num_transfer_tokens, min=0) |
|
|
| |
| |
| values, idx = torch.sort(confidence, dim=1, descending=True) |
|
|
| B, L = confidence.shape |
| |
| cols = torch.arange(L, device=confidence.device).unsqueeze(0).expand(B, L) |
| k_expanded = num_transfer_tokens.unsqueeze(1).expand(B, L) |
| select_sorted = cols < k_expanded |
|
|
| |
| |
| transfer_int = torch.zeros(B, L, device=confidence.device, dtype=torch.int8) |
| transfer_int = transfer_int.scatter(1, idx, select_sorted.to(torch.int8)) |
| transfer_index = transfer_int.bool() & mask_index |
|
|
| return x0, transfer_index |
|
|
| def get_transfer_index_dynamic(logits, temperature, remasking, mask_index, x, num_transfer_tokens, factor=1): |
| logits_with_noise = add_gumbel_noise(logits, temperature=temperature) |
| x0 = torch.argmax(logits_with_noise, dim=-1) |
| if remasking == 'low_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 remasking == 'random': |
| x0_p = torch.rand((x0.shape[0], x0.shape[1]), device=x0.device) |
| else: |
| raise NotImplementedError(remasking) |
| |
| x0 = torch.where(mask_index, x0, x) |
| confidence = torch.where(mask_index, x0_p, -np.inf) |
|
|
| transfer_index = torch.zeros_like(x0, dtype=torch.bool, device=x0.device) |
| num_transfer_tokens = mask_index.sum(dim=1, keepdim=True) |
| |
| for j in range(confidence.shape[0]): |
| num_tokens = int(num_transfer_tokens[j].item()) |
| if num_tokens == 0: |
| continue |
| |
| ns=list(range(1,num_transfer_tokens[j]+1)) |
| es=[factor/(n+1) for n in ns] |
| threshs=[1-e for e in es] |
|
|
| |
| threshs[0]=-1 |
| sorted_confidence=torch.sort(confidence[j][mask_index[j]],dim=-1,descending=True)[0] |
| assert len(sorted_confidence)==len(threshs) |
| for top_i in range(len(threshs)): |
| if sorted_confidence[top_i]<threshs[top_i]: |
| break |
|
|
| if top_i == 0 or top_i == len(threshs)-1: |
| top_i+=1 |
|
|
| _, select_index = torch.topk(confidence[j], k=top_i) |
| transfer_index[j, select_index] = True |
|
|
| return x0, transfer_index |
|
|
| def main(): |
| device = 'cuda' |
|
|
| |
| |
|
|
| model = LLaDAModelLM.from_pretrained('GSAI-ML/LLaDA-8B-Instruct', trust_remote_code=True, torch_dtype=torch.bfloat16).to(device).eval() |
| tokenizer = AutoTokenizer.from_pretrained('GSAI-ML/LLaDA-8B-Instruct', trust_remote_code=True) |
| prompt = "Lily can run 12 kilometers per hour for 4 hours. After that, she runs 6 kilometers per hour. How many kilometers can she run in 8 hours?" |
|
|
| |
| m = [{"role": "user", "content": prompt}, ] |
| prompt = tokenizer.apply_chat_template(m, add_generation_prompt=True, tokenize=False) |
|
|
| input_ids = tokenizer(prompt)['input_ids'] |
| input_ids = torch.tensor(input_ids).to(device).unsqueeze(0) |
| with torch.inference_mode(): |
| nvtx.range_push("INFER") |
|
|
| out = generate_with_dual_cache(model, input_ids, steps=128, gen_length=128, block_length=32, temperature=0., remasking='low_confidence') |
| |
| torch.cuda.synchronize() |
| nvtx.range_pop() |
| print(tokenizer.batch_decode(out[0][:, input_ids.shape[1]:], skip_special_tokens=True)[0]) |
|
|
| if __name__ == '__main__': |
| main() |
|
|