File size: 12,497 Bytes
ddf8c5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env bash
# k3-test/20_k3_experiments.sh β€” K3 experiments E0–E8 (adapted to llama.cpp mmap reality).
#
# IMPORTANT FRAMING (see PREP_REPORT.md):
#   UD-Q4_K_XL = 1508.7 GB, box has ~516 GB RAM + 96 GB VRAM. Full residency is
#   IMPOSSIBLE β€” mmap page-cache streaming IS the deployment mode being tested.
#   "RAM-resident hot set" == warm page cache; "SSD cold tier" == after drop_caches.
#
# Run a subset with: EXP_LIST="E1 E2" ./20_k3_experiments.sh
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/common.sh"

[[ -f $K3_MODEL_PATH ]] || die "K3 not downloaded β€” run ./01_download_k3.sh (and check config.env K3_SUBDIR)"
CHAT="$K3TEST_ROOT/prompts/chat.txt"; CODING="$K3TEST_ROOT/prompts/coding.txt"; LONG="$K3TEST_ROOT/prompts/longdoc.txt"
EXP_LIST="${EXP_LIST:-E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 E10 E11 E12 E13}"

TSPLIT_HOME="${TSPLIT_HOME:-1,1,1}"   # 3x16GB = 48GB = home 2x3090 VRAM budget
BASE=( -m "$K3_MODEL_PATH" -ngl 999 --tensor-split "$TSPLIT_HOME" --cpu-moe )

have_exp() { [[ " $EXP_LIST " == *" $1 "* ]]; }
save_flagset() { printf '%s\n' "$*" > "$LOG_ROOT/$1.flagset" 2>/dev/null || true; }

# ============================================================================
# E0 β€” trunk split: 1 vs 2 vs 3 GPUs. UD-Q4_K_XL dense trunk is mostly Q8_0
#      (~55-60 GB) so a single 24 GB card can't hold it; llama.cpp spills the
#      remainder to CPU automatically β€” that spill cost is exactly what we measure.
# ============================================================================
if have_exp E0; then
  info "=== E0 trunk split sweep ==="
  # HOME CONSTRAINT: 2x3090 = 48 GB VRAM. This box's cards are 16 GB, so:
  #   3 cards (1,1,1)   = 48 GB  == exact home VRAM budget. Trunk ~58 GB Q8_0
  #                        does NOT fit -> ~10 GB trunk spills to RAM (re-read/token).
  #   2 cards (1,1)     = 32 GB  == tighter (e.g. single 3090 + 8 GB elsewhere).
  #   4 cards (1,1,1,1) = 64 GB  == reference "if you added a 3rd 3090-class card".
  drop_caches
  run_exp k3_E0a_home48g  "$CHAT" 128 "${BASE[@]}" --tensor-split 1,1,1     || true
  drop_caches
  run_exp k3_E0b_home32g  "$CHAT" 128 "${BASE[@]}" --tensor-split 1,1       || true
  drop_caches
  run_exp k3_E0c_3card64g "$CHAT" 128 "${BASE[@]}" --tensor-split 1,1,1,1   || true
fi

# ============================================================================
# E1 β€” decode baseline with hot expert set RAM-resident (warm page cache).
#      Two identical passes: pass A cold (post drop_caches) = streaming,
#      pass B warm = RAM-resident ceiling for this prompt set.
# ============================================================================
if have_exp E1; then
  info "=== E1 cold vs warm decode ==="
  drop_caches
  run_exp k3_E1a_cold "$CODING" 128 "${BASE[@]}"                              || true
  run_exp k3_E1b_warm "$CODING" 128 "${BASE[@]}"                              || true
  run_exp k3_E1c_warm_chat "$CHAT" 128 "${BASE[@]}"                           || true
  # length-graded prompts: disk-read (expert streaming) vs prompt length
  for L in 2048 8192; do
    GP="$K3TEST_ROOT/prompts/gen/coding_${L}tok.txt"
    [[ -f $GP ]] || continue
    run_exp "k3_E1_len${L}" "$GP" 64 "${BASE[@]}"                             || true
  done
fi

# ============================================================================
# E9 β€” pp scaling (prompt-processing tok/s at 512/2K/8K), warm process,
#      model-load excluded by llama-bench design. Expert-streaming io deltas
#      still visible via the run's cgroup io accounting.
# ============================================================================
if have_exp E9; then
  info "=== E9 pp scaling (llama-bench) ==="
  PP_SIZES="${PP_SIZES:-512 2048 8192}" GEN="${GEN:-32}" \
    "$SCRIPT_DIR/15_pp_scaling.sh" k3_E9 \
    -m "$K3_MODEL_PATH" -ngl 999 --tensor-split "$TSPLIT" --cpu-moe -t "$THREADS" || true
fi

# ============================================================================
# E2/E3 β€” "prefetch" efficacy, approximated with kernel facilities:
#   E2-on  : mmap + enlarged readahead (layer-sequential access pattern benefits)
#   E3-off : --load-mode dio (direct I/O = every expert read hits NVMe, no cache)
# iostat logs give bytes/token; comparing to E1b isolates SSD-tier cost.
# ============================================================================
if have_exp E2; then
  info "=== E2 mmap + readahead boost ==="
  for f in /sys/block/*/queue/read_ahead_kb; do echo 4096 | $SUDO tee "$f" >/dev/null || echo "CONTAINER_LIMITED: readahead unchanged ($f)" >&2; done
  drop_caches
  run_exp k3_E2_ra4096_cold "$CODING" 128 "${BASE[@]}"                        || true
  for f in /sys/block/*/queue/read_ahead_kb; do echo 128  | $SUDO tee "$f" >/dev/null || true; done
fi

if have_exp E3; then
  info "=== E3 direct-io, no page cache (cold every token) ==="
  drop_caches
  run_exp k3_E3_dio "$CODING" 64 "${BASE[@]}" --load-mode dio                 || true
fi

# ============================================================================
# E4 β€” cache-size sweep: pin RAM with a /dev/shm filler to shrink page cache,
#      emulating 200/320/450-expert hot sets on the home build's 768 GB.
#      (Rental: ~490 GB usable. Home: ~700 GB usable. Filler emulates smaller.)
#      FILLER_GB_LIST tunes the effective cache: cache β‰ˆ RAM_free_at_start βˆ’ filler.
# ============================================================================
if have_exp E4; then
  info "=== E4 cache-size sweep (RAM filler) ==="
  for filler in ${FILLER_GB_LIST:-0 128 256}; do
    unfill_ram; [[ $filler -gt 0 ]] && fill_ram "$filler" || true
    drop_caches
    run_exp "k3_E4_filler${filler}g_cold" "$CODING" 96 "${BASE[@]}"           || true
    run_exp "k3_E4_filler${filler}g_warm" "$CODING" 96 "${BASE[@]}"           || true
    unfill_ram
  done
fi

# ============================================================================
# E5 β€” prompt-lookup (n-gram) speculative decoding. Dynamic cache is written
#      during generation; second pass reads it back for accept-length stats.
# ============================================================================
if have_exp E5; then
  info "=== E5 n-gram speculative β€” spec-only AND spec+expert-offload (the realistic case) ==="
  # baseline for comparison is E1b (same prompt/config, no spec)
  run_exp k3_E5a_ngram_simple "$CODING" 128 "${BASE[@]}" --spec-type ngram-simple || true
  run_exp k3_E5b_ngram_mapk   "$CODING" 128 "${BASE[@]}" --spec-type ngram-map-k  || true
  run_exp k3_E5c_ngram_cache  "$CODING" 128 "${BASE[@]}" --spec-type ngram-cache  || true
  # NOTE: BASE already includes --cpu-moe (K3 can't fit in VRAM), so every E5 run IS
  # the realistic combined case: experts streaming + spec. E1b is the no-spec baseline
  # with identical offload. (Spec-without-offload is impossible for K3 on this box.)
fi

# ============================================================================
# E10 β€” K3-trained DSpark drafter (EAGLE-3-class, real MTP descendant) vs n-gram.
#       The actual deployment-grade speculative path. Needs 03_download_draft.sh.
# ============================================================================
if have_exp E10; then
  info "=== E10 DSpark draft-model speculative ==="
  DRAFT="$MODEL_DIR/k3-draft/draft.gguf"
  if [[ -e "$DRAFT" ]]; then
    run_exp k3_E10a_dspark        "$CODING" 128 "${BASE[@]}" --spec-type draft-dspark -md "$DRAFT" || true
    run_exp k3_E10b_dspark_chat   "$CHAT"   128 "${BASE[@]}" --spec-type draft-dspark -md "$DRAFT" || true
    grep -iE "accept|draft" "$LOG_ROOT/k3_E10a_dspark/run.log" | tail -4 || true
  else
    echo "E10 SKIPPED: no draft at $DRAFT (run ./03_download_draft.sh)" | tee "$LOG_ROOT/k3_E10_SKIPPED.txt"
  fi
fi

# ============================================================================
# E6 β€” GPU hot-expert offload: send experts of the FIRST HOT_LAYERS layers to the
#      GPUs not carrying the trunk (VRAM-permitting). Pattern uses verified tensor
#      names: blk.N.ffn_{up,gate,down}_exps.weight
# ============================================================================
if have_exp E6; then
  info "=== E6 GPU hot-expert offload ==="
  # HOT_EXPERT_RE must match layer indices, e.g. "(0|[1-9]|1[01])" = layers 0–11.
  # Expert tensor names verified in llama.cpp src/llama-arch.cpp:
  #   blk.N.ffn_gate_exps.weight / ffn_up_exps.weight / ffn_down_exps.weight
  HOT_EXPERT_RE="${HOT_EXPERT_RE:-(0|[1-9]|1[01])}"
  # NOTE: with the 48GB home budget the trunk already over-fills VRAM (58>48), so
  # GPU hot-expert offload is NOT available at home unless trunk is requantized to ~Q4.
  # This E6 variant is a REFERENCE (trunk 4 cards + hot experts on CUDA4) to price that.
  # trunk on CUDA0-3 (TSPLIT=1,1,1,1); hot-layer experts to the spare bank CUDA4
  run_exp k3_E6_hotgpu "$CODING" 128 \
      -m "$K3_MODEL_PATH" -ngl 999 --tensor-split 1,1,1,1 \
      -ot "blk.${HOT_EXPERT_RE}.ffn_(up|gate|down)_exps.weight=CUDA4,${EXPERT_CPU_OT}" || true
fi

# ============================================================================
# E7 β€” long-context probe: 8K prefill then decode; state = 24-layer MLA KV
#      + KDA recurrent state. Reports pp tok/s (prefill cost) and whether decode
#      speed degrades vs E1. Runs at CTX_SIZE from config (default 8192).
# ============================================================================
if have_exp E7; then
  info "=== E7 long-context probe (CTX=$CTX_SIZE) ==="
  drop_caches
  CTX_SIZE="$CTX_SIZE" run_exp k3_E7_longctx "$LONG" 128 "${BASE[@]}"         || true
  grep -E "n_kv|recurrent|state size|llama_kv|memory" "$LOG_ROOT/k3_E7_longctx/run.log" \
      > "$LOG_ROOT/k3_E7_longctx/state_notes.txt" 2>/dev/null || true
fi

# ============================================================================
# E11 β€” thread-count sweep: SMT (112) vs physical cores (56). Bandwidth-bound MoE
#       GEMM often runs FASTER on physical cores only. Home build = 32C/64T, so the
#       ratio transfers.
# ============================================================================
if have_exp E11; then
  info "=== E11 thread sweep (home-relevant: 3 GPUs, 48GB) ==="
  run_exp k3_E11a_t112 "$CODING" 96 "${BASE[@]}" -t 112 || true
  run_exp k3_E11b_t56  "$CODING" 96 "${BASE[@]}" -t 56  || true
fi

# ============================================================================
# E12 β€” concurrency: 2 simultaneous decode streams share the page-cached expert
#       reads -> aggregate tok/s is the multi-session answer (plan: 2-4 sessions).
# ============================================================================
if have_exp E12; then
  info "=== E12 two concurrent streams (48GB config) ==="
  start_monitors "$LOG_ROOT/k3_E12_concurrent"
  run_llama k3_E12_s1 "$CODING" 96 "${BASE[@]}" & P1=$!
  run_llama k3_E12_s2 "$CHAT"   96 "${BASE[@]}" & P2=$!
  wait $P1 $P2 || true
  stop_monitors
fi

# ============================================================================
# E13 β€” DSpark draft tuning: --draft-max (num speculative tokens) sweep.
# ============================================================================
if have_exp E13; then
  info "=== E13 DSpark draft-depth tuning ==="
  DRAFT="$MODEL_DIR/k3-draft/draft.gguf"
  if [[ -e "$DRAFT" ]]; then
    for dm in 3 5 7; do
      run_exp "k3_E13_dmax${dm}" "$CODING" 128 "${BASE[@]}" \
          --spec-type draft-dspark -md "$DRAFT" --draft-max "$dm" || true
    done
  fi
fi

# ============================================================================
# E8 β€” top-k override 16 -> 12. llama.cpp has no router top-k override flag for
#      kimi-k3 as of LLAMA_REF; probing and logging UNSUPPORTED is the deliverable.
# ============================================================================
if have_exp E8; then
  if "$LLAMA_BIN/llama-cli" --help 2>&1 | grep -qiE "router|top-k.*expert|expert.*top-k"; then
    info "=== E8 top-k override flag exists β€” see help dump ==="
    "$LLAMA_BIN/llama-cli" --help 2>&1 | grep -iE "router|top-k|expert" > "$LOG_ROOT/k3_E8_flags.txt"
    run_exp k3_E8_topk12 "$CHAT" 128 "${BASE[@]}" --router-top-k 12 || true
  else
    info "=== E8 UNSUPPORTED: no router top-k override in this llama.cpp build β€” logged ==="
    echo "UNSUPPORTED as of $LLAMA_REF" | tee "$LOG_ROOT/k3_E8_UNSUPPORTED.txt"
  fi
fi

info "=== all requested experiments done. Logs in $LOG_ROOT ==="
ls -dt "$LOG_ROOT"/k3_* 2>/dev/null | head -20