| """DllmBlock and DllmBlockBuffer - extracted to avoid circular import with mixin.""" |
|
|
| from __future__ import annotations |
|
|
| import weakref |
| import torch |
|
|
| from dataclasses import dataclass, field |
|
|
| from diffulex.config import DecodingThresholds |
| from diffulex.engine.status import DllmBlockStatus, DllmBlockType |
|
|
| from typing import TYPE_CHECKING |
|
|
| if TYPE_CHECKING: |
| from diffulex.engine.request import DllmReq |
| from diffulex.engine.dllm_block import DllmBlockBuffer |
|
|
| weakref_fn = lambda x: weakref.ref(x) if x is not None else None |
|
|
|
|
| @dataclass |
| class DllmBlock: |
| block_id: int |
| start: int |
| end: int |
| block_size: int |
| mask_token_id: int |
| thresholds: DecodingThresholds |
|
|
| status: DllmBlockStatus = None |
| prev_block: "DllmBlock" = None |
|
|
| block_type: DllmBlockType = DllmBlockType.IN_CONTEXT |
| editable_start: int = 0 |
| commit_ready: bool = False |
| same_as_previous: bool = False |
| same_token_ratio: float = 0.0 |
| all_confident: bool = False |
| post_edit_steps: int = 0 |
| total_steps: int = 0 |
|
|
| def __repr__(self): |
| prev_block_id = self.prev_block.block_id if self.prev_block is not None else None |
| return f"DllmBlock(block_id={self.block_id}, start={self.start}, end={self.end}, block_size={self.block_size}, mask_token_id={self.mask_token_id}, thresholds={self.thresholds}, status={self.status}, prev_block_id={prev_block_id}, is_last_in_context={self.is_last_in_context})" |
|
|
| def post_init_dllm_block(self, req: "DllmReq", dllm_block_buffer: "DllmBlockBuffer"): |
| assert self.end - self.start == self.block_size |
| if not 0 <= int(self.editable_start) <= self.block_size: |
| raise ValueError( |
| f"editable_start must be in [0, {self.block_size}], got: {self.editable_start}" |
| ) |
|
|
| if req is not None: |
| self.bind_req(req) |
|
|
| if dllm_block_buffer is not None: |
| self.bind_buffer(dllm_block_buffer) |
|
|
| if self.status is None: |
| self.status = DllmBlockStatus.TO_CACHE if self.is_complete else DllmBlockStatus.ACTIVE |
| if self.is_complete: |
| self.commit_ready = True |
|
|
| self.make_in_context() |
|
|
| def bind_req(self, req: "DllmReq" | None): |
| self._req = weakref_fn(req) |
|
|
| def bind_buffer(self, dllm_block_buffer: "DllmBlockBuffer" | None): |
| self._dllm_block_buffer = weakref_fn(dllm_block_buffer) |
|
|
| def __getstate__(self): |
| state = self.__dict__.copy() |
| state.pop("_req", None) |
| state.pop("_dllm_block_buffer", None) |
| return state |
|
|
| def __setstate__(self, state): |
| self.__dict__.update(state) |
|
|
| @property |
| def req(self) -> "DllmReq": |
| ref = getattr(self, "_req", None) |
| return ref() if ref else None |
|
|
| @property |
| def dllm_block_buffer(self) -> "DllmBlockBuffer": |
| ref = getattr(self, "_dllm_block_buffer", None) |
| return ref() if ref else None |
|
|
| @property |
| def rel_page_id(self) -> int: |
| return self.start // self.req.page_size |
| |
| @property |
| def token_ids(self) -> list[int]: |
| return self.req[self.start : self.end] |
|
|
| @property |
| def editable_relative_ids(self) -> list[int]: |
| return list(range(int(self.editable_start), self.block_size)) |
|
|
| @property |
| def mask_token_relative_ids(self) -> list[int]: |
| editable_start = int(self.editable_start) |
| return [ |
| i |
| for i, token_id in enumerate(self.token_ids) |
| if i >= editable_start and token_id == self.mask_token_id |
| ] |
|
|
| @property |
| def mask_token_global_ids(self) -> list[int]: |
| editable_start = int(self.editable_start) |
| return [ |
| i + self.start |
| for i, token_id in enumerate(self.token_ids) |
| if i >= editable_start and token_id == self.mask_token_id |
| ] |
|
|
| @property |
| def in_buffer_block_id(self) -> int: |
| return self.dllm_block_buffer.block_ids.index(self.block_id) |
|
|
| @property |
| def num_mask_tokens(self): |
| return sum(token_id == self.mask_token_id for token_id in self.token_ids) |
|
|
| @property |
| def progress(self): |
| return (self.block_size - self.num_mask_tokens) / self.block_size |
|
|
| @property |
| def is_complete(self): |
| return self.progress == 1.0 |
|
|
| @property |
| def is_semi_complete(self): |
| return self.progress >= self.thresholds.semi_complete_threshold |
|
|
| @property |
| def should_force_decode_topk(self): |
| return self.prev_block is not None and self.prev_block.is_semi_complete |
|
|
| @property |
| def should_add_block(self): |
| return ( |
| self.progress >= self.thresholds.add_block_threshold |
| and self.same_token_ratio >= self.thresholds.token_stability_threshold |
| and not self.is_last_in_context |
| ) |
|
|
| @property |
| def is_dummy(self): |
| return self.status == DllmBlockStatus.DUMMY |
|
|
| @property |
| def is_active(self): |
| return self.status == DllmBlockStatus.ACTIVE |
|
|
| @property |
| def is_to_cache(self): |
| return self.status == DllmBlockStatus.TO_CACHE |
|
|
| @property |
| def is_in_cache(self): |
| return self.status == DllmBlockStatus.IN_CACHE |
|
|
| @property |
| def is_in_context(self): |
| return self.block_type == DllmBlockType.IN_CONTEXT |
|
|
| @property |
| def is_out_of_context(self): |
| return self.block_type == DllmBlockType.OUT_OF_CONTEXT |
|
|
| @property |
| def is_last_in_context(self): |
| return self.block_type == DllmBlockType.LAST_IN_CONTEXT |
|
|
| def write_token(self, token_id: int, rel_idx: int): |
| if int(rel_idx) < int(self.editable_start): |
| raise ValueError( |
| f"Cannot write non-editable token in block {self.block_id}: " |
| f"rel_idx={rel_idx}, editable_start={self.editable_start}" |
| ) |
| self.req.token_ids[self.start + rel_idx] = token_id |
| self.commit_ready = False |
|
|
| def write_tokens_parallel(self, token_ids: torch.Tensor, abs_ids: torch.Tensor): |
| token_ids_list = token_ids.tolist() if isinstance(token_ids, torch.Tensor) else list(token_ids) |
| abs_ids_list = abs_ids.tolist() if isinstance(abs_ids, torch.Tensor) else list(abs_ids) |
| for abs_idx, token_id in zip(abs_ids_list, token_ids_list): |
| if int(abs_idx) - self.start < int(self.editable_start): |
| raise ValueError( |
| f"Cannot write non-editable token in block {self.block_id}: " |
| f"abs_idx={abs_idx}, editable_start={self.editable_start}" |
| ) |
| self.req.token_ids[int(abs_idx)] = int(token_id) |
| if abs_ids_list: |
| self.commit_ready = False |
|
|
| def to_cache(self): |
| if self.is_active: |
| self.status = DllmBlockStatus.TO_CACHE |
|
|
| def in_cache(self): |
| if self.is_to_cache: |
| self.status = DllmBlockStatus.IN_CACHE |
|
|
| def make_in_context(self): |
| self.block_type = DllmBlockType.IN_CONTEXT |
|
|
| def make_out_of_context(self): |
| self.block_type = DllmBlockType.OUT_OF_CONTEXT |
|
|
| def make_last_in_context(self): |
| if self.is_in_context: |
| self.block_type = DllmBlockType.LAST_IN_CONTEXT |
|
|
|
|
| @dataclass |
| class DllmBlockBuffer: |
| buffer_size: int |
| dllm_blocks: list[DllmBlock] = field(default_factory=list) |
|
|
| def __repr__(self): |
| return f"DllmBlockBuffer(buffer_size={self.buffer_size}, dllm_blocks={self.dllm_blocks})" |
|
|
| def post_init_dllm_block_buffer(self, req: "DllmReq"): |
| assert len(self.dllm_blocks) == self.buffer_size |
|
|
| if req is not None: |
| self.bind_req(req) |
|
|
| if len(self.dllm_blocks) > 0: |
| for block in self.dllm_blocks: |
| block.post_init_dllm_block(None, self) |
|
|
| def bind_req(self, req: "DllmReq" | None): |
| self._req = weakref_fn(req) |
|
|
| def __getstate__(self): |
| state = self.__dict__.copy() |
| state.pop("_req", None) |
| return state |
|
|
| def __setstate__(self, state): |
| self.__dict__.update(state) |
|
|
| @property |
| def req(self) -> "DllmReq": |
| ref = getattr(self, "_req", None) |
| return ref() if ref else None |
|
|
| @property |
| def buffer_sequence(self) -> list[int]: |
| return self.req[self.dllm_blocks[0].start : self.dllm_blocks[-1].end] |
|
|
| @property |
| def buffer_position_ids(self) -> list[int]: |
| return list(range(self.dllm_blocks[0].start, self.dllm_blocks[-1].end)) |
|
|
| @property |
| def block_ids(self) -> list[int]: |
| return [block.block_id for block in self.dllm_blocks] |
|
|
| @property |
| def cursor_slot_idx(self) -> int: |
| return len(self.valid_blocks) |
|
|
| @property |
| def valid_blocks(self) -> list[DllmBlock]: |
| return [block for block in self.dllm_blocks if not block.is_dummy] |
|
|
| @property |
| def dummy_blocks(self) -> list[DllmBlock]: |
| return [block for block in self.dllm_blocks if block.is_dummy] |
|
|
| @property |
| def active_blocks(self) -> list[DllmBlock]: |
| return [block for block in self.dllm_blocks if block.is_active] |
|
|
| @property |
| def to_cache_blocks(self) -> list[DllmBlock]: |
| return [block for block in self.dllm_blocks if block.is_to_cache] |
|
|
| @property |
| def in_cache_blocks(self) -> list[DllmBlock]: |
| return [block for block in self.dllm_blocks if block.is_in_cache] |
|
|
| @property |
| def first_running_block(self) -> DllmBlock: |
| return self.dllm_blocks[0] |
|
|
| @property |
| def last_running_block(self) -> DllmBlock: |
| return self.dllm_blocks[-1] |
|
|
| @property |
| def first_valid_block(self) -> DllmBlock: |
| return self.dllm_blocks[0] |
|
|
| @property |
| def last_valid_block(self) -> DllmBlock: |
| return self.dllm_blocks[self.cursor_slot_idx - 1] |
|
|
| @property |
| def first_to_cache_block(self) -> DllmBlock: |
| return self.to_cache_blocks[0] |
|
|
| @property |
| def last_to_cache_block(self) -> DllmBlock: |
| return self.to_cache_blocks[-1] |
|
|
| @property |
| def slot_block(self) -> DllmBlock: |
| return self.dllm_blocks[self.cursor_slot_idx] |
|
|
| @property |
| def num_valid_blocks(self) -> int: |
| return self.cursor_slot_idx |
|
|
| @property |
| def num_running_blocks(self) -> int: |
| return self.buffer_size |
|
|
| @property |
| def should_add_block(self) -> bool: |
| return self.last_valid_block.should_add_block |
|
|
| @property |
| def is_overflow(self) -> bool: |
| return self.cursor_slot_idx >= self.buffer_size |
|
|
| @property |
| def prev_step_popped(self) -> bool: |
| if len(self.dllm_blocks) < 2: |
| return False |
| return self.dllm_blocks[-1].block_id == self.dllm_blocks[-2].block_id |
|
|
| def push_back(self, block: DllmBlock): |
| self.dllm_blocks[-1] = block |
|
|
| def pop_front(self): |
| for i in range(0, self.buffer_size - 1): |
| self.dllm_blocks[i] = self.dllm_blocks[i + 1] |
|
|
| def activate_cursor_slot_block(self): |
| self.slot_block.status = DllmBlockStatus.ACTIVE |
| |
| def maybe_fix_context_management(self): |
| if self.first_valid_block.is_dummy and self.first_valid_block.is_last_in_context: |
| self.first_valid_block.prev_block.make_last_in_context() |
|
|