|
53 | 53 | _SPLITMIX_M2 = 0x94D049BB133111EB |
54 | 54 | _PLE_LAYER_PRIME = 10007 |
55 | 55 | _PLE_CACHE_SLAB_ROWS = 1 << 16 |
| 56 | +_TOKEN_INDEX_CACHE_SIZE = 64 |
| 57 | +_FUSED_HASH_ENV = "FREETOKEN_PLE_FUSED_HASH" |
56 | 58 |
|
57 | 59 | logger = init_logger(__name__) |
58 | 60 |
|
59 | 61 |
|
| 62 | +def _fused_row_ids_enabled() -> bool: |
| 63 | + """Enable the fused hash unless the environment explicitly disables it.""" |
| 64 | + return (os.getenv(_FUSED_HASH_ENV) or "1").strip() not in ( |
| 65 | + "0", |
| 66 | + "false", |
| 67 | + "False", |
| 68 | + ) |
| 69 | + |
| 70 | + |
60 | 71 | class _IOVec(ctypes.Structure): |
61 | 72 | _fields_ = [("base", ctypes.c_void_p), ("length", ctypes.c_size_t)] |
62 | 73 |
|
@@ -2644,6 +2655,7 @@ def __init__(self, args: Qwen4ExpArgs, table: PLETableBackend | None = None) -> |
2644 | 2655 | self._table = table |
2645 | 2656 | self._host_hash_constants: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None |
2646 | 2657 | self._host_hash_buffers: tuple[torch.Tensor, ...] | None = None |
| 2658 | + self._token_index_cache: dict[tuple, Tuple[torch.Tensor, torch.Tensor]] = {} |
2647 | 2659 |
|
2648 | 2660 | def attach_table(self, table: PLETableBackend) -> None: |
2649 | 2661 | self._table = table |
@@ -2775,8 +2787,84 @@ def _shift_ignore_eos(self, packed: torch.Tensor) -> List[torch.Tensor]: |
2775 | 2787 | shifted.append(torch.where(valid, gathered, packed.new_full((), self.eos_token_id))) |
2776 | 2788 | return shifted |
2777 | 2789 |
|
| 2790 | + def _token_index(self, meta: PLEMetadata) -> Tuple[torch.Tensor, torch.Tensor]: |
| 2791 | + """Return each token's request index and request-local offset.""" |
| 2792 | + device = meta.input_ids.device |
| 2793 | + num_tokens = meta.input_ids.numel() |
| 2794 | + capturing = device.type == "cuda" and torch.cuda.is_current_stream_capturing() |
| 2795 | + key = ( |
| 2796 | + meta.is_decode, |
| 2797 | + (num_tokens,) if meta.is_decode else tuple(meta.seq_lens), |
| 2798 | + str(device), |
| 2799 | + ) |
| 2800 | + # A graph must own its index allocations. Reusing an eager memo entry here |
| 2801 | + # lets normal cache eviction free storage whose pointers were baked into the |
| 2802 | + # captured Triton launch. |
| 2803 | + cached = None if capturing else self._token_index_cache.get(key) |
| 2804 | + if cached is not None: |
| 2805 | + return cached |
| 2806 | + if meta.is_decode: |
| 2807 | + index = ( |
| 2808 | + torch.arange(num_tokens, dtype=torch.int32, device=device), |
| 2809 | + torch.zeros(num_tokens, dtype=torch.int32, device=device), |
| 2810 | + ) |
| 2811 | + else: |
| 2812 | + cu = meta.cu_seqlens.long() |
| 2813 | + flat_pos = torch.arange(num_tokens, device=device) |
| 2814 | + req = (torch.searchsorted(cu, flat_pos, right=True) - 1).clamp_( |
| 2815 | + max=len(meta.seq_lens) - 1 |
| 2816 | + ) |
| 2817 | + index = ( |
| 2818 | + req.to(torch.int32), |
| 2819 | + (flat_pos - cu[req]).to(torch.int32), |
| 2820 | + ) |
| 2821 | + if not capturing: |
| 2822 | + if len(self._token_index_cache) >= _TOKEN_INDEX_CACHE_SIZE: |
| 2823 | + self._token_index_cache.pop(next(iter(self._token_index_cache))) |
| 2824 | + self._token_index_cache[key] = index |
| 2825 | + return index |
| 2826 | + |
| 2827 | + def _use_fused_row_ids(self, meta: PLEMetadata) -> bool: |
| 2828 | + from freetoken.kernel.backend import is_triton_installed |
| 2829 | + |
| 2830 | + device = meta.input_ids.device |
| 2831 | + if ( |
| 2832 | + device.type != "cuda" |
| 2833 | + or not is_triton_installed() |
| 2834 | + or not _fused_row_ids_enabled() |
| 2835 | + ): |
| 2836 | + return False |
| 2837 | + return all( |
| 2838 | + tensor.device == device |
| 2839 | + for tensor in ( |
| 2840 | + meta.ngram_context, |
| 2841 | + self.layer_multipliers, |
| 2842 | + self.ngram_heads_vocab_sizes, |
| 2843 | + self.ngram_heads_offsets, |
| 2844 | + ) |
| 2845 | + ) |
| 2846 | + |
2778 | 2847 | def row_ids(self, meta: PLEMetadata) -> torch.Tensor: |
2779 | 2848 | """Global table row per (token, hash head): ``[T, num_ngram_heads]`` int64.""" |
| 2849 | + if self._use_fused_row_ids(meta): |
| 2850 | + from freetoken.kernel.triton.ple_hash import ple_row_ids |
| 2851 | + |
| 2852 | + req, local = self._token_index(meta) |
| 2853 | + return ple_row_ids( |
| 2854 | + meta.input_ids.long(), |
| 2855 | + meta.ngram_context, |
| 2856 | + req, |
| 2857 | + local, |
| 2858 | + self.layer_multipliers, |
| 2859 | + self.ngram_heads_vocab_sizes, |
| 2860 | + self.ngram_heads_offsets, |
| 2861 | + eos_token_id=self.eos_token_id, |
| 2862 | + heads_per_ngram=self.heads_per_ngram, |
| 2863 | + ) |
| 2864 | + return self.row_ids_reference(meta) |
| 2865 | + |
| 2866 | + def row_ids_reference(self, meta: PLEMetadata) -> torch.Tensor: |
| 2867 | + """Torch implementation retained as the CPU path and fused-kernel oracle.""" |
2780 | 2868 | packed, select = self._window(meta) |
2781 | 2869 | tokens = [select(s) for s in self._shift_ignore_eos(packed)] |
2782 | 2870 | blocks = [] |
|
0 commit comments