| 422 | |
| 423 | // Update last_used on disk. |
| 424 | entry.last_used = now_unix(); |
| 425 | entry.hits++; |
| 426 | // Optionally rewrite header timestamp (non-critical, skip for perf). |
| 427 | return true; |
| 428 | } |
| 429 | |
| 430 | // ─── Save ─────────────────────────────────────────────────────────────── |
| 431 | |
| 432 | bool DiskPrefixCache::save(int slot, const std::vector<int32_t> & prompt_ids) { |
| 433 | if (disabled()) return false; |
| 434 | |
| 435 | // Learn layout on first save. |
| 436 | if (!layout_known_) { |
| 437 | learn_layout(slot); |
| 438 | if (!layout_known_) return false; |
| 439 | } |
| 440 | |
| 441 | // Check minimum token threshold. |
| 442 | if ((int)prompt_ids.size() < config_.min_tokens) return false; |
| 443 | |
| 444 | auto ref = backend_.snapshot_ref(slot); |
| 445 | if (!ref.ctx) return false; |
| 446 | |
| 447 | PrefixHash hash = hash_prefix(prompt_ids.data(), (int)prompt_ids.size()); |
| 448 | |
| 449 | std::lock_guard<std::mutex> lock(mu_); |
| 450 | |
| 451 | // Skip if already on disk. |
| 452 | if (find_entry(hash) >= 0) return true; |
| 453 | |
| 454 | // Pre-write budget check: estimate file size and reject if it would |
| 455 | // exceed budget even after evicting all evictable entries. |
| 456 | if (config_.budget_bytes > 0) { |
| 457 | uint64_t payload = 0; |
| 458 | uint32_t ntens = 0; |
| 459 | size_t table_est = 0; |
| 460 | for (ggml_tensor * t = ggml_get_first_tensor(ref.ctx); t; |
| 461 | t = ggml_get_next_tensor(ref.ctx, t)) { |
| 462 | ntens++; |
| 463 | payload += ggml_nbytes(t); |
| 464 | table_est += 2 + std::strlen(t->name) + 4 + 32 + 8; // name_len + name + type + ne[4] + nbytes |
| 465 | } |
| 466 | size_t est_size = DISK_CACHE_HEADER_SIZE + table_est + payload; |
| 467 | if (est_size > config_.budget_bytes) { |
| 468 | std::fprintf(stderr, "[disk-cache] skip save: estimated %.1f MB exceeds budget\n", |
| 469 | (double)est_size / (1024.0 * 1024.0)); |
| 470 | return false; |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | std::string path = make_path(hash); |
| 475 | std::string tmp_path = path + ".tmp"; |
| 476 | |
| 477 | if (!write_file(tmp_path, ref, prompt_ids)) { |
| 478 | std::remove(tmp_path.c_str()); |
| 479 | return false; |
| 480 | } |
| 481 | |