1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
from vllm.v1.engine import FinishReason
4
-from vllm.v1.metrics.stats import IterationStats, RequestStateStats
4
+from vllm.v1.metrics.stats import IterationStats, PromptTokenStats, RequestStateStats
5
6
7
def test_iteration_stats_repr():
107
finished_req2.num_cached_tokens, 0
108
)
109
assert prefill_kv_computed2 == 0 # All cached, nothing computed
110
+
111
+
112
+def test_prompt_token_stats_all_computed():
113
+ """Test all tokens computed locally, no caching."""
114
+ stats = PromptTokenStats()
115
+
116
+ # Case 1: No caching (All tokens computed locally)
117
+ stats.update_from_output(
118
+ num_cached_tokens=0,
119
+ num_external_computed_tokens=0,
120
+ prompt_len=1000,
121
+ )
122
+
123
+ assert stats.computed == 1000
124
+ assert stats.local_cache_hit == 0
125
+ assert stats.external_kv_transfer == 0
126
+ assert stats.total == 1000
127
+
128
+
129
+def test_prompt_token_stats_partial_local_cache():
130
+ """Test partial local prefix cache hit."""
131
+ stats = PromptTokenStats()
132
+
133
+ # Case 2: Partial local cache
134
+ stats.update_from_output(
135
+ num_cached_tokens=300,
136
+ num_external_computed_tokens=0,
137
+ prompt_len=1000,
138
+ )
139
+
140
+ assert stats.computed == 700
141
+ assert stats.local_cache_hit == 300
142
+ assert stats.external_kv_transfer == 0
143
+
144
+
145
+def test_prompt_token_stats_partial_external_transfer():
146
+ """Test partial external KV transfer."""
147
+ stats = PromptTokenStats()
148
+
149
+ # Case 3: Partial external transfer
150
+ stats.update_from_output(
151
+ num_cached_tokens=500,
152
+ num_external_computed_tokens=500,
153
+ prompt_len=1000,
154
+ )
155
+
156
+ assert stats.computed == 500
157
+ assert stats.local_cache_hit == 0
158
+ assert stats.external_kv_transfer == 500
159
+
160
+
161
+def test_prompt_token_stats_mixed_sources():
162
+ """Test mix of local cache and external transfer."""
163
+ stats = PromptTokenStats()
164
+
165
+ # Case 4: Mixed sources
166
+ stats.update_from_output(
167
+ num_cached_tokens=600,
168
+ num_external_computed_tokens=200,
169
+ prompt_len=1000,
170
+ )
171
+
172
+ assert stats.computed == 400
173
+ assert stats.local_cache_hit == 400
174
+ assert stats.external_kv_transfer == 200
175
+
176
+
177
+def test_prompt_token_stats_full_local_cache_recompute():
178
+ """Test full local cache triggers last token recomputation.
179
+
180
+ When all tokens are cached, the scheduler reduces num_cached_tokens by 1
181
+ to force the model to recompute the last token.
182
+ """
183
+ stats = PromptTokenStats()
184
+
185
+ # Case 5: Full local cache (999 cached after reduction, 1 recomputed)
186
+ stats.update_from_output(
187
+ num_cached_tokens=999,
188
+ num_external_computed_tokens=0,
189
+ prompt_len=1000,
190
+ )
191
+
192
+ assert stats.computed == 1
193
+ assert stats.local_cache_hit == 1000
194
+ assert stats.recomputed_tokens == 1
195
+
196
+
197
+def test_prompt_token_stats_full_external_transfer_recompute():
198
+ """Test full external transfer triggers last token recomputation."""
199
+ stats = PromptTokenStats()
200
+
201
+ # Case 6: Full external transfer (999 cached after reduction, 1 recomputed)
202
+ stats.update_from_output(
203
+ num_cached_tokens=999,
204
+ num_external_computed_tokens=1000,
205
+ prompt_len=1000,
206
+ )
207
+
208
+ assert stats.computed == 1
209
+ assert stats.local_cache_hit == 0
210
+ assert stats.external_kv_transfer == 1000
211
+ assert stats.recomputed_tokens == 1