hash_cache.rs 5.17 KB
Newer Older
1
2
3
4
5
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::common::evictor::LRUEvictor;
use dynamo_tokens::blocks::UniqueBlock;
6
use rustc_hash::FxHashMap;
7
8
9
10

/// Hash-based KV cache with O(1) block lookups, maintaining active (ref-counted) and
/// inactive (LRU-evictable) pools.
pub struct HashCache {
11
    active_blocks: FxHashMap<UniqueBlock, usize>,
12
13
14
15
16
17
18
19
    inactive_blocks: LRUEvictor<UniqueBlock>,
    max_capacity: usize,
}

impl HashCache {
    /// Create a new HashCache with the given maximum block capacity.
    pub fn new(max_capacity: usize) -> Self {
        Self {
20
            active_blocks: FxHashMap::default(),
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
            inactive_blocks: LRUEvictor::default(),
            max_capacity,
        }
    }

    /// Get the reference count of an active block, if it exists.
    pub fn get_active_ref_count(&self, block: &UniqueBlock) -> Option<usize> {
        self.active_blocks.get(block).copied()
    }

    /// Increment the reference count of an active block. Returns the new count.
    pub fn increment_ref(&mut self, block: &UniqueBlock) -> usize {
        let ref_count = self
            .active_blocks
            .get_mut(block)
            .expect("block must be active to increment ref");
        *ref_count += 1;
        *ref_count
    }

    /// Decrement the reference count of an active block. Returns the new count.
    pub fn decrement_ref(&mut self, block: &UniqueBlock) -> usize {
        let ref_count = self
            .active_blocks
            .get_mut(block)
            .expect("block must be active to decrement ref");
        *ref_count -= 1;
        *ref_count
    }

    /// Insert a block into the active pool with the given reference count.
    pub fn insert_active(&mut self, block: UniqueBlock, ref_count: usize) {
        self.active_blocks.insert(block, ref_count);
    }

    /// Remove a block from the active pool. Returns the reference count, or None if not found.
    pub fn remove_active(&mut self, block: &UniqueBlock) -> Option<usize> {
        self.active_blocks.remove(block)
    }

    /// Check if a block is in the active pool.
    pub fn contains_active(&self, block: &UniqueBlock) -> bool {
        self.active_blocks.contains_key(block)
    }

    /// Insert a block into the inactive pool (LRU order).
    pub fn insert_inactive(&mut self, block: UniqueBlock) {
        self.inactive_blocks.insert(block);
    }

    /// Remove a block from the inactive pool. Returns true if it was found.
    pub fn remove_inactive(&mut self, block: &UniqueBlock) -> bool {
        self.inactive_blocks.remove(block)
    }

    /// Evict the least-recently-used block from the inactive pool.
    pub fn evict_inactive(&mut self) -> Option<UniqueBlock> {
        self.inactive_blocks.evict()
    }

    /// Check if a block is in the inactive pool.
    pub fn contains_inactive(&self, block: &UniqueBlock) -> bool {
        self.inactive_blocks.contains(block)
    }

    /// Check if a block exists in either active or inactive pool.
    pub fn contains(&self, block: &UniqueBlock) -> bool {
        self.active_blocks.contains_key(block) || self.inactive_blocks.contains(block)
    }

    /// Move block from active to inactive (ref_count reached 0).
    pub fn deactivate(&mut self, block: &UniqueBlock) {
        debug_assert!(
            self.active_blocks.contains_key(block),
            "deactivate called on non-active block"
        );
        debug_assert!(
            !self.inactive_blocks.contains(block),
            "deactivate called on already-inactive block"
        );
        self.active_blocks.remove(block);
        self.inactive_blocks.insert(block.clone());
    }

    /// Move block from inactive to active with ref_count=1. Returns true if found.
    pub fn reactivate(&mut self, block: &UniqueBlock) -> bool {
        if self.inactive_blocks.remove(block) {
            self.active_blocks.insert(block.clone(), 1);
            true
        } else {
            false
        }
    }

    /// Check if total blocks (active + inactive) has reached max_capacity.
    pub fn is_at_capacity(&self) -> bool {
        self.active_blocks.len() + self.inactive_blocks.len() >= self.max_capacity
    }

    /// Get the number of active blocks.
    pub fn num_active(&self) -> usize {
        self.active_blocks.len()
    }

    /// Get the number of inactive blocks.
    pub fn num_inactive(&self) -> usize {
        self.inactive_blocks.len()
    }

    /// Get the maximum block capacity.
    pub fn max_capacity(&self) -> usize {
        self.max_capacity
    }

    /// Get the current capacity (active + inactive blocks).
    pub fn current_capacity(&self) -> usize {
        self.active_blocks.len() + self.inactive_blocks.len()
    }

    /// Iterate over active block keys.
    pub fn active_keys(&self) -> impl Iterator<Item = &UniqueBlock> {
        self.active_blocks.keys()
    }

    /// Iterate over inactive block keys.
    pub fn inactive_keys(&self) -> impl Iterator<Item = &UniqueBlock> {
        self.inactive_blocks.keys()
    }

    /// Direct access to active blocks map (for tests that check ref counts).
151
    pub fn active_blocks(&self) -> &FxHashMap<UniqueBlock, usize> {
152
153
154
        &self.active_blocks
    }
}