Spaces:
Running
Running
| # Phase 1 β Gemma-4-26B-A4B teacher capture: feasibility & proof | |
| **Box:** pop β RTX 5070 Ti (16 GB, sm_120 Blackwell), 31 GB RAM, torch 2.11+cu128, | |
| transformers 5.12, bitsandbytes 0.49.2. Date: 2026-06-18. | |
| **Goal (Phase 1 only):** make the Gemma-4 **text decoder** runnable forward-only for | |
| activation capture on this hardware, and prove the capture works. No training yet. | |
| ## TL;DR | |
| - **It runs.** The 26.5B teacher's text decoder does forward-only capture on the 16 GB | |
| card via **bf16 layer-streaming** (accelerate offload to the fast NVMe). Capture | |
| proven correct on a 32,768-token slice: all 30 layers, correct shapes, finite, Xβ Y. | |
| - **bnb-4bit does NOT work here** β neither fully-resident (OOMs at load) nor offloaded | |
| (meta quant_state crash). Details below. | |
| - **The real blocker is STORAGE, not compute.** Storing block I/O (X and Y) for all 30 | |
| layers costs **330 KiB/token** β **6.15 TiB for 20M tokens, 15.4 TiB for 50M** | |
| (50M *exceeds* the 6.3 TB free on the cache drive). And the cache drive is a slow USB | |
| HDD (111 MB/s write), which makes the capture **write-bound at ~330 tok/s** β ~17 h | |
| for 20M tokens. This needs a decision before a real run β see NEEDS_MIKEY.txt. | |
| ## What is captured (verified against the reference forward) | |
| In `Gemma4TextDecoderLayer.forward` the FF/MoE sub-block is, after the attention residual: | |
| ``` | |
| residual = hidden_states # X = FF-block input (post-attention) | |
| h = pre_feedforward_layernorm(residual) | |
| h = mlp(h) # shared (dense) expert | |
| if moe: # 8-of-128 routed experts (GeGLU) | |
| h1 = post_feedforward_layernorm_1(h) | |
| _, w, idx = router(residual.flatten) | |
| h2 = experts(pre_feedforward_layernorm_2(residual.flatten), idx, w) | |
| h = h1 + post_feedforward_layernorm_2(h2) | |
| h = post_feedforward_layernorm(h) # D = FF-block delta | |
| hidden_states = residual + h # Y = X + D = FF-block output | |
| hidden_states *= layer_scalar # (AFTER the block β NOT captured) | |
| ``` | |
| Capture hooks: **X** = forward-PRE hook on `pre_feedforward_layernorm` (its input); | |
| **D** = forward hook on `post_feedforward_layernorm` (its output); stored **Y = X + D**. | |
| `hidden_size_per_layer_input = 0`, so the per-layer-input branch is inert. This is the | |
| exact block the student replaces, so (X, Y) is the correct feature-distillation pair. | |
| ## Load methods evaluated | |
| | method | result | | |
| |---|---| | |
| | **bf16 layer-streaming (accelerate, NVMe offload)** | β **WORKS.** `device_map=auto`, `max_memory={0:6GiB, cpu:16GiB}`, `offload_folder` on NVMe. 4 layers GPU-resident / 9 CPU / 23 disk. Reuses the real Gemma4 forward. | | |
| | bnb-4bit, fully resident on GPU | β text decoder ~**14.4 GiB**; load OOMs on the final ~484 MiB expert tensor. No room to finish loading, let alone a forward. (The bnb 4-bit *kernel* runs fine on sm_120 β verified with a standalone quantize+matmul; it's purely the size.) | | |
| | bnb-4bit + accelerate CPU/disk offload | β loads, but **forward dies**: offloaded quant_state tensors stay on `meta` β `NotImplementedError: Cannot copy out of meta tensor`. (Also needed a monkeypatch just to get *past* hook-attach, where bnb does `offset.item()` on a meta tensor.) bnb-4bit is fundamentally not offloadable on this stack. | | |
| | `--cpu-layers` (per-layer device_map β CPU bf16) | β **silently ignored** by transformers 5.12's new `core_model_loading`; the `'':0` catch-all wins, GPU use unchanged (identical OOM for cpu-layers 2 and 8). | | |
| | disabling warmup / serializing the loader (1 worker) | β no effect on the 14.4 GiB resident wall. | | |
| | AWQ / compressed-tensors / FP8 prequantized checkpoint | β none exists for this brand-new model; 4-bit AWQ would hit the same ~14 GiB wall, FP8 (~26 GiB) is far too big. | | |
| Two small, documented monkeypatches live in `capture_gemma.py` for the 4-bit *diagnostic* | |
| path only (`patch_disable_warmup`, `patch_serial_load`, `patch_bnb_meta_quantstate`); the | |
| working bf16 path needs none of them. | |
| ## Throughput (measured) | |
| - Forward-only (no writes), bf16 streaming, ctx=256: | |
| - batch 16 β **153 tok/s** (peak 10.4 GiB) | |
| - batch 64 β **558 tok/s** (peak 12.1 GiB) β bigger batch amortises the ~29 s/forward | |
| NVMe weight transfer over more tokens. This is the lever; batch is bounded by the | |
| MoE tokenΓtop_k activation expansion. | |
| - Full capture incl. writes (batch 16) β **97 tok/s**, fwd peak 7.06 GiB (write-bound). | |
| - Key infra: NVMe read **3.0 GB/s** (teacher); USB-HDD write **111 MB/s** (cache drive). | |
| ## Capture proof (validation slice) | |
| `capture_gemma.py --val --batch 16 --shard-tokens 8192` β `/mnt/data/cache/gemma_cap_val` | |
| (32,768 tokens). Verified by `inspect_capture.py`: | |
| - 30 layer dirs; each `layerNN/{input,output}_NNNNN.pt`, 4 shards/tensor (sharding works). | |
| - Shapes `[8192, 2816]` bf16, all finite; Xβ Y with FF-block relative delta 0.93β3.36. | |
| - **330 KiB/token** total (X+Y over 30 layers) = 11,264 B/token/layer. | |
| ## Size & wall-time estimates for a real capture | |
| At 330 KiB/token, and (for wall-time) batchβ64 so forward (β558 tok/s) exceeds the HDD | |
| write rate β **write-bound at β330 tok/s**: | |
| | corpus | cache size | wall-time (write-bound) | fits 6.3 TB free? | | |
| |---|---|---|---| | |
| | 20M tokens | **6.15 TiB** | ~17 h | barely | | |
| | 50M tokens | **15.4 TiB** | ~43 h | **NO** | | |
| (If left at batch 16 it is forward-bound instead: ~57 h for 20M. Use batch β₯48.) | |
| ## Recommendation / open decision (see /tmp/NEEDS_MIKEY.txt) | |
| The forward problem is solved; the **storage** cost is the gate. Options, in order of | |
| my preference: | |
| 1. **Don't pre-materialize all 30 layers' X,Y to disk.** Re-stream the teacher *during* | |
| training (compute distillation targets on the fly), or capture/train a few layers per | |
| pass. Removes the multi-TB cache entirely. (Phase-2 design change.) | |
| 2. **fp8 storage** halves the cache (165 KiB/tok β 3.1 TiB @ 20M) and roughly doubles the | |
| write-bound rate; verify distillation tolerates fp8 targets. | |
| 3. **Cap the budget at ~15β20M tokens** on the existing USB HDD (~6 TiB, ~17 h) β feasible | |
| today but uses nearly all free space. | |
| 4. **Rent a one-off 80 GB GPU** for a fast capture pass β fixes *speed* (full model | |
| resident, no offload, hours not days) but **not storage**; you'd still need ~6β15 TB of | |
| fast scratch, so pair it with option 1 or 2. | |
| ## Files | |
| - `capture_gemma.py` β capture (bf16-stream default; `--method 4bit` reproduces the OOM). | |
| - `test_stream_bf16.py` β standalone streaming throughput probe. | |
| - `inspect_capture.py` β shard validation + size/time estimator. | |
| - Validation output: `/mnt/data/cache/gemma_cap_val/` (meta.json + sample shards). | |