| 623 | // ─── File I/O: Write ──────────────────────────────────────────────────── |
| 624 | |
| 625 | bool DiskPrefixCache::write_file(const std::string & path, |
| 626 | const ModelBackend::SnapshotRef & ref, |
| 627 | const std::vector<int32_t> & prompt_ids) { |
| 628 | FILE * f = std::fopen(path.c_str(), "wb"); |
| 629 | if (!f) return false; |
| 630 | |
| 631 | // Count tensors and compute payload size. |
| 632 | uint32_t n_tensors = 0; |
| 633 | uint64_t payload_bytes = 0; |
| 634 | for (ggml_tensor * t = ggml_get_first_tensor(ref.ctx); t; t = ggml_get_next_tensor(ref.ctx, t)) { |
| 635 | n_tensors++; |
| 636 | payload_bytes += ggml_nbytes(t); |
| 637 | } |
| 638 | |
| 639 | // Write header. |
| 640 | DiskCacheHeader hdr{}; |
| 641 | std::memcpy(hdr.magic, "DKVC", 4); |
| 642 | hdr.version = DISK_CACHE_VERSION; |
| 643 | std::memcpy(hdr.layout_id, layout_id_.data(), 16); |
| 644 | hdr.cur_pos = (uint32_t)ref.cur_pos; |
| 645 | hdr.n_tensors = n_tensors; |
| 646 | hdr.token_count = (uint32_t)prompt_ids.size(); |
| 647 | PrefixHash ph = hash_prefix(prompt_ids.data(), (int)prompt_ids.size()); |
| 648 | std::memcpy(hdr.token_hash, ph.data(), 16); |
| 649 | hdr.payload_bytes = payload_bytes; |
| 650 | hdr.created_at = now_unix(); |
| 651 | hdr.last_used = hdr.created_at; |
| 652 | hdr.last_tok = ref.last_tok; |
| 653 | |
| 654 | if (!write_header(f, hdr)) { std::fclose(f); return false; } |
| 655 | |
| 656 | // Write tensor table. |
| 657 | for (ggml_tensor * t = ggml_get_first_tensor(ref.ctx); t; t = ggml_get_next_tensor(ref.ctx, t)) { |
| 658 | uint16_t name_len = (uint16_t)std::strlen(t->name); |
| 659 | write_u16(f, name_len); |
| 660 | std::fwrite(t->name, 1, name_len, f); |
| 661 | write_u32(f, (uint32_t)t->type); |
| 662 | for (int d = 0; d < 4; ++d) write_i64(f, t->ne[d]); |
| 663 | write_u64(f, ggml_nbytes(t)); |
| 664 | } |
| 665 | |
| 666 | // Write tensor data. |
| 667 | std::vector<uint8_t> buf(4 * 1024 * 1024); // 4 MB transfer buffer |
| 668 | for (ggml_tensor * t = ggml_get_first_tensor(ref.ctx); t; t = ggml_get_next_tensor(ref.ctx, t)) { |
| 669 | size_t nbytes = ggml_nbytes(t); |
| 670 | size_t offset = 0; |
| 671 | while (offset < nbytes) { |
| 672 | size_t chunk = std::min(buf.size(), nbytes - offset); |
| 673 | ggml_backend_tensor_get(t, buf.data(), offset, chunk); |
| 674 | if (std::fwrite(buf.data(), 1, chunk, f) != chunk) { |
| 675 | std::fclose(f); |
| 676 | return false; |
| 677 | } |
| 678 | offset += chunk; |
| 679 | } |
| 680 | } |
| 681 | |
| 682 | std::fclose(f); |