Download reference/code/T3D/train/prepare.py from Ouzhang/hku_diffusion_dllm: direct link, hf CLI and curl.
- Browser
- Download file 18.6 kB
-
https://huggingface.co/Ouzhang/hku_diffusion_dllm/resolve/main/reference/code/T3D/train/prepare.py
- Command line
-
hf download hf://Ouzhang/hku_diffusion_dllm/reference/code/T3D/train/prepare.py
-
curl -L -o prepare.py https://huggingface.co/Ouzhang/hku_diffusion_dllm/resolve/main/reference/code/T3D/train/prepare.py
18.6 kB
| import torch | |
| def make_basic_block_attention( | |
| N: int, | |
| start_pos: int, # = L0 | |
| block_size: int, # = b | |
| ) -> torch.Tensor: | |
| B = 1 | |
| L0 = start_pos | |
| L1 = (N - L0) // 2 # N = L0 + 2·L1 | |
| assert L0 + 2 * L1 == N, "input length must be L0 + 2*L1" | |
| # all -inf first | |
| bias = torch.full((B, 1, N, N), 0) | |
| rows = torch.arange(L0 + L1, L0 + 2 * L1) # (L1,) | |
| rows_token = torch.arange(L0, L0 + L1) # (L1,) | |
| # update block by block | |
| for bi in range((L1 + block_size - 1) // block_size): | |
| # [bi*b , min((bi+1)*b, L1)) | |
| left_end = L0 + min((bi) * block_size, L1) | |
| right_start= L0 + L1 + (left_end - L0) | |
| i_start = bi * block_size | |
| i_end = min((bi + 1) * block_size, L1) # no i_end | |
| block_rows = rows[i_start:i_end] | |
| bias[:, :, block_rows.unsqueeze(-1), 0:left_end] = 1 | |
| bias[:, :, block_rows.unsqueeze(-1), right_start:(right_start + block_size)] = 1 | |
| block_rows = rows_token[i_start:i_end] | |
| left_end = L0 + min((bi + 1) * block_size, L1) | |
| bias[:, :, block_rows.unsqueeze(-1), 0:left_end] = 1 | |
| if L0 > 0: | |
| num_blocks_pre = (L0 + block_size - 1) // block_size | |
| for bi in range(num_blocks_pre): | |
| # row interval [row_start, row_end) | |
| row_end = max(L0 - bi * block_size, 0) | |
| row_start = max(L0 - (bi + 1) * block_size, 0) | |
| if row_end > row_start: | |
| block_rows = torch.arange(row_start, row_end) | |
| bias[:, :, block_rows.unsqueeze(-1), 0:row_end] = 1 | |
| return bias # (B,1,N,N) | |
| def process_pad(attn, input_ids, L0, L1, start_pos, pad_id): | |
| N = L0 + 2 * L1 | |
| device = input_ids.device | |
| cols = torch.arange(N, device=device) # (N,) | |
| key_mask = (cols < start_pos).unsqueeze(0) & (input_ids == pad_id) # (B, N) | |
| # set -inf | |
| attn.masked_fill_(key_mask[:, None, None, :], 0) | |
| # avoid +-inf or none in forward | |
| A = attn[:, 0] # (B, N, N) | |
| bad = (A.sum(dim=-1) == 0) & (torch.arange(A.size(1), device=A.device).unsqueeze(0) < start_pos) | |
| b, r = bad.nonzero(as_tuple=True) | |
| A[b, r, :] = 0; A[b, r, r] = 1 | |
| return attn | |
| def one_round_vectorized(input_ids_b, step_map_b, L0, L1, block_size, mask_id): | |
| """ | |
| Perform a single "round" on one sample b: | |
| - For each block, take the minimum non -1 value in step_map. | |
| - Create pmask (positions equal to the block minimum). | |
| - Create a noise mask for the extended segment (positions >= block minimum). | |
| - Mark the chosen minimum positions in step_map as -1 for the next round. | |
| Returns: | |
| extended_input_ids_b : Tensor with duplicated + masked response segment | |
| pmask_b : Boolean mask for tokens selected in this round | |
| new_step_map_b : Updated step_map (selected positions set to -1) | |
| has_any : Whether any position was selected in this round | |
| """ | |
| device = input_ids_b.device | |
| NB = (L1 + block_size - 1) // block_size | |
| pad_len = NB * block_size - L1 | |
| # Reshape step_map into [NB, block_size], fill last incomplete block with -1 | |
| step_pad = torch.full((NB * block_size,), -1, dtype=torch.long, device=device) | |
| step_pad[:L1] = step_map_b | |
| step_blk = step_pad.view(NB, block_size) # [NB, Bk] | |
| valid = step_blk.ge(0) # Valid positions (not -1) | |
| big = torch.iinfo(step_blk.dtype).max | |
| tmp = step_blk.masked_fill(~valid, big) # Fill invalid positions with a large value | |
| min_vals, _ = tmp.min(dim=1, keepdim=True) # Current minimum for each block | |
| # Select positions equal to block minimum (only valid positions) | |
| pmask_blk = step_blk.eq(min_vals) & valid | |
| if not pmask_blk.any(): | |
| # No positions left to select in this round | |
| return None, None, step_map_b, False | |
| # Noise mask for extended segment: mark positions >= block minimum | |
| ge_mask_blk = step_blk.ge(min_vals) & valid # [NB, Bk] | |
| # Flatten back to length L1 (discard padding) | |
| pmask_tail = pmask_blk.view(-1)[:L1] # [L1] | |
| ge_mask_tail = ge_mask_blk.view(-1)[:L1] # [L1] | |
| # Construct pmask_b: [0:L0] = False, [L0:] = pmask_tail | |
| pmask_b = torch.zeros(L0 + L1, dtype=torch.bool, device=device) | |
| pmask_b[L0:] = pmask_tail | |
| # Build extended segment: duplicate response and replace noise positions with mask_id | |
| tail = input_ids_b[L0:L0+L1].clone() | |
| tail[ge_mask_tail] = mask_id | |
| extended_input_ids_b = torch.empty(L0 + L1 + L1, dtype=input_ids_b.dtype, device=device) | |
| extended_input_ids_b[:L0+L1] = input_ids_b | |
| extended_input_ids_b[L0+L1:] = tail | |
| # Update step_map: mark selected minimum positions as -1 for the next round | |
| new_step_map_b = step_map_b.clone() | |
| new_step_map_b[pmask_tail] = -1 | |
| return extended_input_ids_b, pmask_b, new_step_map_b, True | |
| def collapse_k_unique(lst, k: int): | |
| if k <= 0: | |
| raise ValueError("k must be > 0") | |
| uniq = sorted(set(lst)) | |
| mapping = {} | |
| n = len(uniq) | |
| for idx, val in enumerate(uniq): | |
| group = idx // k | |
| end_idx = min((group + 1) * k - 1, n - 1) | |
| rep = uniq[end_idx] | |
| mapping[val] = rep | |
| return [mapping[x] for x in lst] | |
| def collect_training_data(config, input_ids, start_pos, pad_id, mask_id, vocab_size=None, post_num=None, step_map_list=None): | |
| B, L = input_ids.shape | |
| L0 = start_pos | |
| L1 = L - L0 | |
| # block_size = config.training.block_size | |
| # lower = config.training.lower_p | |
| # upper = config.training.upper_p | |
| if config.training.method == "semi-ar": | |
| # Get mask_ratios from config (e.g., [1.0, 0.75, 0.5, 0.25] for variable mask ratios) | |
| # If not specified, defaults to [1.0] (fully masked, backward compatible) | |
| mask_ratios = config.training.get("mask_ratios", None) | |
| if mask_ratios is None: | |
| mask_ratios = [1.0] # Default: fully masked (backward compatible) | |
| elif isinstance(mask_ratios, (int, float)): | |
| mask_ratios = [mask_ratios] # Convert single value to list | |
| else: | |
| # Convert OmegaConf ListConfig to Python list if needed | |
| try: | |
| mask_ratios = list(mask_ratios) | |
| except (TypeError, AttributeError): | |
| pass # Already a list or compatible type | |
| # Get random_ratio from config, default to 0.0 (no random tokens, only mask tokens) | |
| random_ratio = config.model.get("random_ratio", 0.0) | |
| # Get mask_strategy from config: "trace" (use step_map decode order) or "random" (random masking) | |
| # Default to "trace" for backward compatibility | |
| mask_strategy = config.training.get("mask_strategy", "trace") | |
| if mask_strategy not in ["trace", "random"]: | |
| raise ValueError(f"mask_strategy must be 'trace' or 'random', got '{mask_strategy}'") | |
| # Get block_size from config | |
| block_size = config.training.block_size | |
| device = input_ids.device | |
| # Calculate probability weights for each mask ratio with exponential scaling | |
| # Use exponential weights to create extreme differences: weights = mask_ratios ** exponent | |
| # Higher mask ratio gets exponentially higher probability | |
| mask_ratio_exponent = config.training.get("mask_ratio_exponent", 4.0) # Default: 4 (exponential scaling) | |
| mask_ratios_tensor = torch.tensor(mask_ratios, dtype=torch.float32, device=device) | |
| weights = mask_ratios_tensor ** mask_ratio_exponent # Exponential weights for extreme distribution | |
| # Normalize weights to get probability distribution | |
| probs = weights / weights.sum() # Probability distribution: higher mask_ratio has exponentially higher probability | |
| # For each input sample, sample one mask_ratio according to the probability distribution | |
| # Higher mask_ratio has higher probability of being selected | |
| selected_mask_ratios_list = [] | |
| # Sample mask_ratio for each input sample | |
| # Use multinomial to sample indices according to probabilities | |
| sampled_indices = torch.multinomial(probs.unsqueeze(0).expand(B, -1), num_samples=1, replacement=True).squeeze(-1) # [B] | |
| selected_mask_ratios_list = [mask_ratios[idx.item()] for idx in sampled_indices] | |
| # Expand step_map if provided (one step_map per sample, matching the sampled mask_ratio) | |
| # Treat empty list as None | |
| step_map_expanded = None | |
| if step_map_list is not None and len(step_map_list) > 0: | |
| step_map_expanded = [] | |
| for b in range(B): | |
| sm = step_map_list[b] | |
| if isinstance(sm, (list, tuple)): | |
| step_map_expanded.append(torch.tensor(sm, dtype=torch.long)) | |
| elif isinstance(sm, torch.Tensor): | |
| step_map_expanded.append(sm.clone()) | |
| else: | |
| step_map_expanded.append(torch.tensor(sm, dtype=torch.long)) | |
| # Each input sample generates exactly one training sample | |
| input_ids_expanded = input_ids # [B, L] - no expansion, one sample per input | |
| expanded_B = B # Batch size remains the same | |
| selected_mask_ratios = torch.tensor(selected_mask_ratios_list, device=device, dtype=torch.float32) # [B] | |
| # 2) Construct the noisy tail | |
| noise_tail = input_ids_expanded[:, L0:].clone() # [expanded_B, L1] | |
| # Create response mask indicating which tokens should be masked | |
| response_mask = torch.zeros(expanded_B, L1, dtype=torch.bool, device=device) # [expanded_B, L1] | |
| # Choose masking strategy based on config and step_map availability | |
| use_trace_masking = (mask_strategy == "trace") and (step_map_expanded is not None) | |
| if use_trace_masking: | |
| # Use step_map to determine decode order for masking | |
| # Lower step_map values = decoded earlier = mask first | |
| step_map_tensors = [] | |
| for sm in step_map_expanded: | |
| if isinstance(sm, (list, tuple)): | |
| step_map_tensors.append(torch.tensor(sm, dtype=torch.long)) | |
| elif isinstance(sm, torch.Tensor): | |
| step_map_tensors.append(sm) | |
| else: | |
| step_map_tensors.append(torch.tensor(sm, dtype=torch.long)) | |
| # Stack step_map tensors, handling variable lengths | |
| # All items in step_map_tensors should already be tensors from the previous loop | |
| max_len = max(sm.shape[0] for sm in step_map_tensors) | |
| step_map_padded = [] | |
| for sm in step_map_tensors: | |
| sm_len = sm.shape[0] | |
| if sm_len < max_len: | |
| # Pad with large values (will be masked anyway) | |
| padding = torch.full((max_len - sm_len,), 999999, dtype=sm.dtype) | |
| sm = torch.cat([sm, padding], dim=0) | |
| elif sm_len > max_len: | |
| sm = sm[:max_len] | |
| step_map_padded.append(sm) | |
| step_map = torch.stack(step_map_padded, dim=0).to(device) # [expanded_B, max_len] | |
| # Trim or pad step_map to match L1 | |
| if step_map.shape[1] > L1: | |
| step_map = step_map[:, :L1] # [expanded_B, L1] | |
| elif step_map.shape[1] < L1: | |
| # Pad with large values | |
| pad_len = L1 - step_map.shape[1] | |
| padding = torch.full((expanded_B, pad_len), 999999, dtype=step_map.dtype, device=device) | |
| step_map = torch.cat([step_map, padding], dim=1) # [expanded_B, L1] | |
| # For each sample, mask tokens according to decode order (step_map) per block | |
| NB = (L1 + block_size - 1) // block_size # Number of blocks | |
| for b in range(expanded_B): | |
| mask_ratio = selected_mask_ratios[b].item() | |
| step_map_b = step_map[b] # [L1] | |
| # Process each block | |
| for bi in range(NB): | |
| block_start = bi * block_size | |
| block_end = min((bi + 1) * block_size, L1) | |
| block_len = block_end - block_start | |
| # Get step_map and indices for this block | |
| block_step_map = step_map_b[block_start:block_end] # [block_len] | |
| block_indices = torch.arange(block_start, block_end, device=device) # Global indices | |
| # Find valid positions in this block (exclude padding values) | |
| valid_mask = block_step_map < 999999 | |
| valid_block_indices = block_indices[valid_mask] # Global indices of valid positions | |
| valid_block_step_map = block_step_map[valid_mask] # Step map values for valid positions | |
| if len(valid_block_indices) > 0: | |
| # Sort by step_map to get decode order (lower = earlier = mask first) | |
| sorted_order = torch.argsort(valid_block_step_map) | |
| sorted_valid_indices = valid_block_indices[sorted_order] # Global indices sorted by decode order | |
| # Number of tokens to mask in this block based on mask_ratio | |
| num_to_mask_in_block = int(len(sorted_valid_indices) * mask_ratio) | |
| if num_to_mask_in_block > 0: | |
| # Mask the first num_to_mask_in_block tokens in decode order within this block | |
| mask_indices_in_block = sorted_valid_indices[:num_to_mask_in_block] | |
| response_mask[b, mask_indices_in_block] = True | |
| else: | |
| # Random masking: randomly mask tokens per block (same as collect_training_data_sft) | |
| NB = (L1 + block_size - 1) // block_size # Number of blocks | |
| for b in range(expanded_B): | |
| mask_ratio = selected_mask_ratios[b].item() | |
| # Process each block | |
| for bi in range(NB): | |
| block_start = bi * block_size | |
| block_end = min((bi + 1) * block_size, L1) | |
| block_len = block_end - block_start | |
| # Number of tokens to mask in this block based on mask_ratio | |
| num_to_mask_in_block = int(block_len * mask_ratio) | |
| if num_to_mask_in_block > 0: | |
| # Randomly select positions to mask within this block | |
| block_positions = torch.randperm(block_len, device=device)[:num_to_mask_in_block] | |
| mask_indices_in_block = block_start + block_positions | |
| response_mask[b, mask_indices_in_block] = True | |
| # 1) Create pmask: prefix all False, response mask based on variable mask ratios | |
| p_mask = torch.cat([ | |
| torch.zeros(expanded_B, L0, dtype=torch.bool, device=device), | |
| response_mask | |
| ], dim=1) # [expanded_B, L] | |
| # Apply masking to noise_tail: tokens that should be masked | |
| if random_ratio > 0 and vocab_size is not None: | |
| # Hybrid strategy: within the masked positions, apply random_ratio | |
| # Some positions get random tokens, others get mask tokens | |
| masked_positions = response_mask # [expanded_B, L1] | |
| # Initialize random_mask to all False | |
| random_mask = torch.zeros(expanded_B, L1, dtype=torch.bool, device=device) | |
| # For each sample, randomly select positions for random tokens within masked positions | |
| for b in range(expanded_B): | |
| masked_idx = torch.where(masked_positions[b])[0] # Positions that should be masked | |
| if len(masked_idx) > 0: | |
| num_random = max(1, int(len(masked_idx) * random_ratio)) # At least 1 if ratio > 0 | |
| num_random = min(num_random, len(masked_idx)) | |
| if num_random > 0: | |
| # Randomly select which masked positions get random tokens | |
| random_idx = masked_idx[torch.randperm(len(masked_idx), device=device)[:num_random]] | |
| random_mask[b, random_idx] = True | |
| mask_token_mask = masked_positions & (~random_mask) | |
| # Replace selected positions with random tokens | |
| if random_mask.any(): | |
| num_random = random_mask.sum().item() | |
| random_tokens = torch.randint(0, vocab_size, (num_random,), | |
| device=device, dtype=noise_tail.dtype) | |
| noise_tail[random_mask] = random_tokens | |
| # Replace remaining masked positions with mask_id | |
| if mask_token_mask.any(): | |
| noise_tail[mask_token_mask] = mask_id | |
| else: | |
| # Simple case: replace all masked positions with mask_id | |
| noise_tail[response_mask] = mask_id | |
| # 3) Concatenate original sequence with noisy tail | |
| extended_input_ids = torch.cat([input_ids_expanded, noise_tail], dim=1) # [expanded_B, L + L1] | |
| else: | |
| raise ValueError(f"Method {config.training.method} not supported") | |
| pad_resp = (extended_input_ids[:, :L] == pad_id) & p_mask | |
| if post_num is not None: | |
| cum_pad = torch.cumsum(pad_resp.int(), dim=1) | |
| p_mask &= ~(pad_resp & (cum_pad > post_num)) | |
| labels = extended_input_ids[:, :L].clone() | |
| idx = torch.arange(L).unsqueeze(0).expand(extended_input_ids.shape[0], -1) | |
| valid = (idx >= start_pos) | extended_input_ids[:, :L].ne(pad_id) | |
| tok_idx = valid.long().cumsum(dim=-1) - 1 | |
| tok_idx = tok_idx.masked_fill(~valid, 1) | |
| tok_idx_resp = tok_idx[:, start_pos:] | |
| tok_idx_ext = torch.cat([tok_idx, tok_idx_resp], dim=1) | |
| keep = p_mask.view(p_mask.size(0), -1).any(dim=1) | |
| extended_input_ids = extended_input_ids[keep] | |
| p_mask = p_mask[keep] | |
| tok_idx_ext = tok_idx_ext[keep] | |
| labels = labels[keep] | |
| return extended_input_ids, p_mask, tok_idx_ext, labels |