| 5604 | } |
| 5605 | |
| 5606 | static bool edgetam_perceiver_forward( |
| 5607 | const sam3_model& model, |
| 5608 | const std::vector<float>& mem_features, // [64 * H * W] |
| 5609 | const std::vector<float>& mem_pos, // [64 * H * W] |
| 5610 | int H, int W, |
| 5611 | std::vector<float>& out_latents, // output: [512 * 64] |
| 5612 | std::vector<float>& out_pos) { // output: [512 * 64] |
| 5613 | auto t_start = std::chrono::high_resolution_clock::now(); |
| 5614 | |
| 5615 | const auto& perc = model.perceiver; |
| 5616 | const int D = 64; |
| 5617 | const int N_1d = 256; // number of 1D latent tokens |
| 5618 | const int N_2d = 256; // number of 2D latent tokens |
| 5619 | const int HW = H * W; |
| 5620 | const int n_layers = (int)perc.layers.size(); |
| 5621 | const int nw = (int)sqrtf((float)N_2d); // 16 windows per spatial dim |
| 5622 | const int ws = H / nw; // window size (4 for H=64, 2 for H=32) |
| 5623 | const int ws2 = ws * ws; // tokens per window |
| 5624 | |
| 5625 | fprintf(stderr, "%s: H=%d W=%d D=%d layers=%d 1d_tokens=%d 2d_tokens=%d ws=%d\n", |
| 5626 | __func__, H, W, D, n_layers, N_1d, N_2d, ws); |
| 5627 | |
| 5628 | if (ws < 1 || H % ws != 0 || W % ws != 0) { |
| 5629 | fprintf(stderr, "%s: H=%d or W=%d not divisible by window_size=%d (nw=%d)\n", |
| 5630 | __func__, H, W, ws, nw); |
| 5631 | return false; |
| 5632 | } |
| 5633 | if (nw * nw != N_2d) { |
| 5634 | fprintf(stderr, "%s: expected %d 2D windows but got %d\n", |
| 5635 | __func__, N_2d, nw * nw); |
| 5636 | return false; |
| 5637 | } |
| 5638 | |
| 5639 | // ── Read learnable latent tokens from model weights ───────────────── |
| 5640 | // latents_1d: ggml shape [64, 256] → ne[0]=64(D), ne[1]=256(N) |
| 5641 | std::vector<float> latents_1d_data(D * N_1d); |
| 5642 | sam3_read_f32(perc.latents_1d, latents_1d_data.data(), D * N_1d); |
| 5643 | |
| 5644 | std::vector<float> latents_2d_data(D * N_2d); |
| 5645 | sam3_read_f32(perc.latents_2d, latents_2d_data.data(), D * N_2d); |
| 5646 | |
| 5647 | // ── Prepare 2D windowed features on CPU ───────────────────────────── |
| 5648 | // mem_features layout: [D, H*W] (ggml: ne[0]=D, ne[1]=H*W, stored as |
| 5649 | // element (d, pos) at flat index d + pos*D where pos = w + h*W). |
| 5650 | // Actually, the features from the memory encoder have layout [D, W, H] in |
| 5651 | // ggml 4D, flattened to [D, H*W]. We need to interpret as a 2D spatial grid. |
| 5652 | // The input is already in [D, H*W] layout with spatial stride such that |
| 5653 | // pos = w + h * W. |
| 5654 | |
| 5655 | // Window partition: [D, H*W] → [D, ws2, N_2d] i.e. [64, 16, 256] |
| 5656 | std::vector<float> feat_windowed(D * ws2 * N_2d); |
| 5657 | edgetam_window_partition_cpu(mem_features.data(), D, H, W, ws, feat_windowed.data()); |
| 5658 | |
| 5659 | // Reshape latents_2d for windowed processing: [D, N_2d] → [D, 1, N_2d] |
| 5660 | // Each window has exactly 1 latent token. |
| 5661 | // The latents_2d weight is [D, 256] = 256 latent tokens. |
| 5662 | // For 2D: batch=256 windows, each with 1 latent and 16 feature tokens. |
| 5663 | // Rearrange latents_2d from [D, 256] to [D, 1, 256] (batch dim = 256) |
no test coverage detected