File size: 10,166 Bytes
2b6ab7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
231
232
diff --git a/ggml/src/ggml-cpu/amx/amx.cpp b/ggml/src/ggml-cpu/amx/amx.cpp
index e90c279..49be9e1 100644
--- a/ggml/src/ggml-cpu/amx/amx.cpp
+++ b/ggml/src/ggml-cpu/amx/amx.cpp
@@ -152,6 +152,13 @@ class extra_buffer_type : ggml::cpu::extra_buffer_type {
         if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(src1)) {
             return false;
         }
+        // SDQ: the AMX weight repack (ggml_backend_amx_convert_weight) only converts the
+        // ne0 x ne1 slice of a tensor, so a 3D/4D weight would be left with unconverted
+        // data in the remaining slices. MLA attention feeds a 3D wv_b to MUL_MAT, which
+        // lands here and then reads garbage (NaN) - keep 3D weights in the CPU buffer.
+        if (src0->ne[2] > 1 || src0->ne[3] > 1) {
+            return false;
+        }
         if (!src0->buffer || src0->buffer->buft != ggml_backend_amx_buffer_type()) {
             return false;
         }
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 97250c2..1fcfb6e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -18,6 +18,9 @@ set(LLAMA_CORE_SOURCES
     llama-cparams.cpp
     llama-grammar.cpp
     llama-graph.cpp
+    sdq_pager.cpp
+    sdq_buft.cpp
+    sdq_moe.cpp
     llama-hparams.cpp
     llama-impl.cpp
     llama-io.cpp
@@ -51,6 +54,11 @@ add_library(llama
             ${LLAMA_MODELS_SOURCES}
             )
 
+# SDQ: chat / benchmark front end (links only llama + ggml)
+add_executable(sdq-chat sdq_cli.cpp)
+target_link_libraries(sdq-chat PRIVATE llama)
+target_include_directories(sdq-chat PRIVATE .)
+
 set_target_properties(llama PROPERTIES
     VERSION ${LLAMA_VERSION_BASE}
     SOVERSION ${LLAMA_VERSION_MAJOR}
@@ -61,11 +69,14 @@ set_target_properties(llama PROPERTIES
 
 # exclude non-model sources from unity build
 set_source_files_properties(${LLAMA_CORE_SOURCES} ../include/llama.h unicode.h
+    sdq_pager.cpp sdq_buft.cpp sdq_moe.cpp
     PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)
 
 configure_file(llama-version.h.in ${CMAKE_CURRENT_BINARY_DIR}/llama-version.h @ONLY)
 
 target_include_directories(llama PRIVATE . ${CMAKE_CURRENT_BINARY_DIR})
+# SDQ needs ggml internal headers (custom buffer type + quantized dot kernels)
+target_include_directories(llama PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../ggml/src)
 target_include_directories(llama PUBLIC ../include)
 target_compile_features   (llama PRIVATE cxx_std_17) # don't bump
 
diff --git a/src/llama-context.cpp b/src/llama-context.cpp
index 9dcaf05..4fd893d 100644
--- a/src/llama-context.cpp
+++ b/src/llama-context.cpp
@@ -4,6 +4,9 @@
 #include "llama-arch.h"
 #include "llama-graph.h"
 #include "llama-impl.h"
+
+// SDQ: expert pager
+#include "sdq_pager.h"
 #include "llama-batch.h"
 #include "llama-io.h"
 #include "llama-memory.h"
@@ -2122,6 +2125,12 @@ int llama_context::decode(const llama_batch_ext & batch_inp) {
     // wait for the computation to finish (automatically done when obtaining the model output)
     //synchronize();
 
+    // ---- SDQ: 收掉這一步 decode 的 accounting。
+    // 這是所有前端都會經過的單一出口;sdq-chat 自己另外驅動,所以不會重複計數。
+    if (sdq::sdq_enabled()) {
+        sdq::sdq_on_decode((int) n_tokens_all);
+    }
+
     return 0;
 }
 
diff --git a/src/llama-graph.cpp b/src/llama-graph.cpp
index 23e4c90..4769929 100644
--- a/src/llama-graph.cpp
+++ b/src/llama-graph.cpp
@@ -1,5 +1,7 @@
 #include "llama-graph.h"
 
+#include "sdq_pager.h"
+
 #include "llama-impl.h"
 #include "llama-model.h"
 #include "llama-moe-cache.h"
@@ -2211,6 +2213,40 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
     //call early so that topk-moe can be used
     ggml_build_forward_expand(gf, weights);
 
+    // SDQ expert pager: route the selected experts through the SSD->RAM pager
+    // instead of ggml_mul_mat_id, so the expert weights never have to be resident.
+    // The router (logits / gating / group top-k / weight norm) is still computed
+    // by ggml above; only "evaluate the selected experts and accumulate" moves.
+    if (const char * dp = getenv("SDQ_GRAPH_DUMP")) {
+        const int dl = getenv("SDQ_GRAPH_DUMP_LAYER") ? atoi(getenv("SDQ_GRAPH_DUMP_LAYER")) : -1;
+        if (dl < 0 || il == dl) {
+            ggml_tensor * c3 = ggml_reshape_3d(ctx0, cur, n_embd, 1, n_tokens);
+            ggml_build_forward_expand(gf, c3);
+            ggml_build_forward_expand(gf, selected_experts);
+            ggml_build_forward_expand(gf, weights);
+            std::string path = std::string(dp) + "." + std::to_string(il);
+            FILE * f = fopen(path.c_str(), "wb");
+            if (f) {
+                const int32_t meta[8] = { (int32_t) n_embd, (int32_t) n_tokens, (int32_t) n_expert_used,
+                                          (int32_t) n_expert, (int32_t) (sdq::sdq_pager_active_for_layer(il) ? 1 : 0), 0, 0, 0 };
+                fwrite(meta, sizeof(int32_t), 8, f);
+                fwrite(c3->data, sizeof(float), (size_t) (n_embd * n_tokens), f);
+                fwrite(selected_experts->data, sizeof(int32_t), (size_t) (n_expert_used * n_tokens), f);
+                fwrite(weights->data, sizeof(float), (size_t) (n_expert_used * n_tokens), f);
+                fclose(f);
+            }
+        }
+    }
+
+    if (sdq::sdq_pager_active_for_layer(il) && cur->ne[0] == n_embd) {
+        const float swiglu_limit = il >= 0 ? hparams.swiglu_clamp_exp[il] : 0.0f;
+        ggml_tensor * moe_out = sdq::sdq_build_moe(gf, ctx0, cur, selected_experts, weights, il,
+                n_expert, n_expert_used, swiglu_limit);
+        cb(moe_out, "ffn_moe_out", il);
+        moe_out = sdq::sdq_maybe_dump_tensor(ctx0, moe_out, il);
+        return moe_out;
+    }
+
     // the experts of host-resident layers may be read from the MoE cache
     ggml_tensor * slots = build_moe_cache_slots(selected_experts, up_exps, gate_exps, down_exps, gate_up_exps, il);
 
@@ -2415,6 +2451,9 @@ ggml_tensor * llm_graph_context::build_moe_ffn(
 
     cb(moe_out, "ffn_moe_out", il);
 
+    // dump the native path too, so it can be diffed against the paged path
+    moe_out = sdq::sdq_maybe_dump_tensor(ctx0, moe_out, il);
+
     return moe_out;
 }
 
@@ -2798,7 +2837,16 @@ ggml_tensor * llm_graph_context::build_attn_mha(
 #else
             // It's preferable to do the calculation as a matrix-matrix multiplication with n_tokens in dimension 1.
             // The permutations are noops and only change how the tensor data is interpreted.
+            // FLASH_ATTN_EXT produces F16, but the CPU backend cannot run MUL_MAT with a
+            // quantized v_mla and F16 activations (widening requires vec_dot_type == F32),
+            // and a quantized weight in the AMX buffer requires contiguous activations.
+            if (cur->type != GGML_TYPE_F32 && v_mla->type != GGML_TYPE_F16 && v_mla->type != GGML_TYPE_F32) {
+                cur = ggml_cast(ctx0, cur, GGML_TYPE_F32);
+            }
             cur = ggml_permute(ctx0, cur, 0, 2, 1, 3);
+            if (!ggml_is_contiguous(cur)) {
+                cur = ggml_cont(ctx0, cur);
+            }
             cur = ggml_mul_mat(ctx0, v_mla, cur);
             cb(cur, "fattn_mla", il);
             cur = ggml_permute(ctx0, cur, 0, 2, 1, 3);
diff --git a/src/llama-model.cpp b/src/llama-model.cpp
index 2e86914..fbf06cb 100644
--- a/src/llama-model.cpp
+++ b/src/llama-model.cpp
@@ -1,5 +1,7 @@
 #include "llama-model.h"
 
+#include "sdq_pager.h"
+
 #include "llama-arch.h"
 #include "llama-ext.h"
 #include "llama-hparams.h"
@@ -1834,7 +1836,9 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
     // per-tensor activation precision policy
     prec_policy.load(ml, *this);
 
-    ml.init_mappings(true, use_mlock ? &pimpl->mlock_mmaps : nullptr);
+    // SDQ: the expert weights are read by the pager with its own pread calls, so the
+    // loader must not MAP_POPULATE / WILLNEED the whole model file into page cache.
+    ml.init_mappings(!sdq::sdq_enabled(), use_mlock ? &pimpl->mlock_mmaps : nullptr);
     pimpl->mappings.reserve(ml.mappings.size());
 
     // create the backend buffers
diff --git a/src/llama.cpp b/src/llama.cpp
index 9beb148..2633d96 100644
--- a/src/llama.cpp
+++ b/src/llama.cpp
@@ -11,6 +11,9 @@
 #include "llama-model-saver.h"
 #include "llama-model.h"
 
+// SDQ: expert pager(SSD -> RAM 分頁),見 sdq-ling/0001-sdq-ling.patch
+#include "sdq_pager.h"
+
 #include "ggml.h"
 #include "ggml-cpp.h"
 #include "ggml-backend.h"
@@ -404,6 +407,27 @@ static struct llama_model * llama_model_load_from_file_impl(
     // TODO: remove
     ggml_time_init();
 
+    // ---- SDQ: 把 MoE expert 權重交給 pager(SSD -> RAM arena 分頁)----
+    // 放在模型載入的唯一入口,這樣 sdq-chat / llama-server / llama-cli 行為一致。
+    //   SDQ_PAGER=0        關閉,回到上游行為(A/B 對照用)
+    //   SDQ_RAM_BUDGET_MB  RAM 總預算,arena 會配合縮減(沒有給就用實體 RAM 的一部分)
+    //   SDQ_ARENA_MB       直接指定 arena 大小
+    //   SDQ_RESERVE_MB     KV/SSM state + compute buffer 的預留
+    //   SDQ_IO_THREADS     I/O 執行緒數、SDQ_PREFETCH=0 關背景預取、SDQ_STATS_FILE 指標輸出
+    {
+        const char * sdq_off = getenv("SDQ_PAGER");
+        if ((!sdq_off || strcmp(sdq_off, "0") != 0) && !params.vocab_only && !path_model.empty()) {
+            if (sdq::sdq_init(path_model)) {
+                // 沒有這一行,load_tensors() 會把整個模型檔要求預取進 page cache,
+                // 正是分頁要避免的事(I/O 稽核見 results/io_audit.md)。
+                sdq::sdq_set_enabled(true);
+                sdq::sdq_install_buft_overrides(&params);
+                // 權重是 pread 進 arena 的,不該再讓後端自己 offload 一次。
+                params.n_gpu_layers = 0;
+            }
+        }
+    }
+
     if (!params.vocab_only && ggml_backend_reg_count() == 0) {
         LLAMA_LOG_ERROR("%s: no backends are loaded. hint: use ggml_backend_load() or ggml_backend_load_all() to load a backend before calling this function\n", __func__);
         return nullptr;