|
| 1 | +# Laguna + Gemma4 Unimplemented Stubs & Fix Plan |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +## Laguna (`dflash/src/laguna/`) |
| 6 | + |
| 7 | +**Status:** Phase 2 complete (forward graph, loader, cache, snapshots, daemon, PFlash compress). |
| 8 | +Phase 3/4 work (sparse prefill, spec decode) not yet started. |
| 9 | + |
| 10 | +### L1. MoE Shared-Only Stub (debug — intentional, no fix needed) |
| 11 | +- **File:** `laguna_target_graph.cpp:287-303` |
| 12 | +- **What:** Env `DFLASH_LAGUNA_MOE_STUB=1` → shared expert only. Full MoE IS the default. |
| 13 | + |
| 14 | +### L2. Dead Code: `build_laguna_moe_block_legacy` (cleanup) |
| 15 | +- **File:** `laguna_target_graph.cpp:386-468` |
| 16 | +- **What:** An alternative MoE implementation never called from anywhere. |
| 17 | +- **Fix:** Delete or gate behind compile flag. |
| 18 | + |
| 19 | +### L3. SWA Ring-Buffer KV Optimization (deferred) |
| 20 | +- **File:** `laguna_target_graph.cpp:544` |
| 21 | +- **What:** All 40 layers allocate full `max_ctx` KV cache. SWA layers only need |
| 22 | + `sliding_window=512` tokens. Wastes ~2.5 GiB at 32K context. |
| 23 | +- **Fix:** Ring-buffer for SWA layers with capacity = 512. |
| 24 | + |
| 25 | +### L4. PFlash Sparse Prefill (Phase 3 — not started) |
| 26 | +- **File:** Not yet created |
| 27 | +- **What:** Laguna uses `ggml_flash_attn_ext` (dense) for all attention. Missing |
| 28 | + block-sparse FA via `flash_prefill_forward_bf16`/`f16`/`q8` on full-attention layers. |
| 29 | +- **Impact:** 2-4× TTFT regression vs what PFlash would give on long contexts. |
| 30 | +- **Fix:** Layer-segmented prefill path (pattern in `qwen3_graph.cpp:488-558`). |
| 31 | + |
| 32 | +### L5. DFlash Speculative Decode (Phase 4 — not started) |
| 33 | +- **File:** `laguna_backend.h` — does NOT override `supports_dflash_spec_decode()` |
| 34 | +- **What:** No `LagunaDFlashTarget` class. No spec decode acceleration. |
| 35 | +- **Fix:** Implement `DFlashTarget` interface (`verify_batch`, `snapshot_kv`, |
| 36 | + `restore_kv`, `is_eos`, `embed_tokens`, `project_hidden_to_tokens`, etc.). |
| 37 | + |
| 38 | +### L6. C++ Cross-Tokenizer Mapping (Phase 4 — Python only) |
| 39 | +- **File:** `bench_laguna_pflash.cpp:88` — uses `fake_l = 1972` placeholder |
| 40 | +- **What:** Qwen3→Laguna vocab mapping only in Python (`laguna_pflash_niah.py:394`). |
| 41 | +- **Fix:** Port `cross_tok_compressed` logic to C++. |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Gemma4 (`dflash/src/gemma4/`) |
| 46 | + |
| 47 | +**Status:** Early integration. Loader, cache, graph, backend, daemon all exist and compile. |
| 48 | +Forward graph is functional but several features are stubbed or missing. |
| 49 | + |
| 50 | +### G1. Per-Layer Embedding Injection NOT Wired (functional gap) |
| 51 | +- **File:** `gemma4_graph.cpp:352-365` |
| 52 | +- **What:** Two explicit TODOs: `gemma4_step()` does not pass token IDs needed for |
| 53 | + per-layer embeddings. `per_layer_input` is always `nullptr` in the layer loop. |
| 54 | + The architecture uses per-layer token embeddings gated into the residual stream — |
| 55 | + this is skipped entirely during forward. |
| 56 | +- **Impact:** Numerically wrong output on models that rely on per-layer embeddings |
| 57 | + (architecture defines `per_layer_tok_embd`, `per_layer_inp_gate`, `per_layer_proj`). |
| 58 | +- **Fix:** Add a `const int32_t * token_ids` parameter to `gemma4_step()`, look up |
| 59 | + per-layer embeddings via the global `per_layer_tok_embd` tensor (sliced per layer), |
| 60 | + project through `per_layer_model_proj` + `per_layer_proj_norm`, and pass result |
| 61 | + to each layer's `build_gemma4_layer()`. |
| 62 | + |
| 63 | +### G2. Park/Unpark Are No-Ops (functional gap) |
| 64 | +- **File:** `gemma4_backend.cpp:70-81` |
| 65 | +- **What:** `park()` sets a flag but does NOT free weights/cache. `unpark()` clears |
| 66 | + the flag but does NOT reload. Unlike Laguna which frees/reloads VRAM. |
| 67 | +- **Impact:** Park command doesn't actually release GPU memory for pflash drafter. |
| 68 | +- **Fix:** Mirror Laguna's pattern: `park()` → `free_gemma4_cache` + `free_gemma4_weights`; |
| 69 | + `unpark()` → `load_gemma4_gguf` + `create_gemma4_cache`. |
| 70 | + |
| 71 | +### G3. PFlash Compress Not Supported (stub) |
| 72 | +- **File:** `gemma4_backend.cpp:342-349` |
| 73 | +- **What:** `handle_compress()` prints "not supported" and returns. No drafter integration. |
| 74 | +- **Impact:** No PFlash compression for Gemma4 targets (longer TTFT on long contexts). |
| 75 | +- **Fix:** Port Laguna's compress path (lazy-load Qwen3-0.6B drafter, score+compress, |
| 76 | + emit surviving tokens). Needs cross-tokenizer mapping if Gemma4 vocab differs. |
| 77 | + |
| 78 | +### G4. DFlash Speculative Decode Not Supported |
| 79 | +- **File:** `gemma4_backend.h` — does NOT override `supports_dflash_spec_decode()` |
| 80 | +- **What:** No `Gemma4DFlashTarget`. Same gap as Laguna. |
| 81 | +- **Fix:** Implement `DFlashTarget` interface for Gemma4. |
| 82 | + |
| 83 | +### G5. SWA Ring-Buffer KV Optimization (same as Laguna) |
| 84 | +- **File:** `gemma4_loader.cpp:334` — SWA layers allocate full `max_ctx` KV |
| 85 | +- **What:** KV cache for SWA layers is full-sized. Only need `sliding_window` tokens. |
| 86 | +- **Fix:** Ring-buffer for SWA layers. |
| 87 | + |
| 88 | +### G6. PFlash Sparse Prefill Not Implemented |
| 89 | +- **File:** `gemma4_graph.cpp` — only uses `ggml_flash_attn_ext` |
| 90 | +- **What:** Same as Laguna — no block-sparse FA for full-attention layers. |
| 91 | +- **Fix:** Layer-segmented prefill with `flash_prefill_forward_*`. |
| 92 | + |
| 93 | +### G7. `restore_and_generate` Resets Cache Position (logic issue) |
| 94 | +- **File:** `gemma4_backend.cpp:245-267` |
| 95 | +- **What:** After restoring snapshot, calls `generate()` which sets `cache_.cur_pos = 0` |
| 96 | + at line 178, discarding the restored position. This means the diff-prefill |
| 97 | + optimization (only re-prefilling tokens after the snapshot) is NOT happening. |
| 98 | +- **Impact:** Every restore+generate does a full re-prefill from token 0. |
| 99 | +- **Fix:** Don't reset `cur_pos` in the restore path; only prefill the diff |
| 100 | + (tokens from `snap.cur_pos` onward). Follow Laguna's `restore_and_generate` pattern. |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +## Combined Summary Table |
| 105 | + |
| 106 | +| ID | Backend | Stub | File | Severity | Effort | |
| 107 | +|----|---------|------|------|----------|--------| |
| 108 | +| G1 | Gemma4 | Per-layer embedding injection not wired (always nullptr) | `gemma4_graph.cpp:352-365` | **Critical** | 1-2d | |
| 109 | +| G7 | Gemma4 | `restore_and_generate` resets cache to 0 (no diff-prefill) | `gemma4_backend.cpp:178,245` | **High** | 0.5d | |
| 110 | +| G2 | Gemma4 | Park/unpark are no-ops (don't free/reload VRAM) | `gemma4_backend.cpp:70-81` | **High** | 0.5d | |
| 111 | +| G3 | Gemma4 | `handle_compress()` prints "not supported" | `gemma4_backend.cpp:342-349` | High | 1d | |
| 112 | +| G6 | Gemma4 | No sparse prefill (dense ggml FA only) | `gemma4_graph.cpp` | High | 2-3d | |
| 113 | +| G4 | Gemma4 | No DFlash speculative decode | `gemma4_backend.h` | High | 3-5d | |
| 114 | +| G5 | Gemma4 | SWA layers allocate full max_ctx KV (no ring buffer) | `gemma4_loader.cpp:334` | Medium | 1-2d | |
| 115 | +| L4 | Laguna | No sparse prefill (dense ggml FA only) | not yet created | **High** | 2-3d | |
| 116 | +| L5 | Laguna | No DFlash speculative decode | `laguna_backend.h` | High | 3-5d | |
| 117 | +| L6 | Laguna | C++ cross-tokenizer mapping missing (Python only) | `bench_laguna_pflash.cpp:88` | Medium | 1-2d | |
| 118 | +| L3 | Laguna | SWA layers allocate full max_ctx KV (no ring buffer) | `laguna_target_graph.cpp:544` | Medium | 1-2d | |
| 119 | +| L2 | Laguna | Dead `build_laguna_moe_block_legacy` never called | `laguna_target_graph.cpp:386-468` | Low | 1h | |
| 120 | +| L1 | Laguna | MoE shared-only stub (env debug switch) | `laguna_target_graph.cpp:287` | None | — | |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Recommended Fix Order |
| 125 | + |
| 126 | +1. **G1** — Per-layer embeddings (Gemma4 produces wrong output without this) |
| 127 | +2. **G7** — restore_and_generate cache reset bug (correctness) |
| 128 | +3. **G2** — Park/unpark no-ops (VRAM management) |
| 129 | +4. **L4 + G6** — PFlash sparse prefill (both archs, biggest perf win) |
| 130 | +5. **G3** — PFlash compress for Gemma4 |
| 131 | +6. **L5 + G4** — DFlash spec decode (both archs) |
| 132 | +7. **L6** — C++ cross-tokenizer |
| 133 | +8. **L3 + G5** — SWA ring-buffer (VRAM optimization) |
| 134 | +9. **L2** — Dead code cleanup |
0 commit comments