File size: 7,984 Bytes
a8b0793
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// SDQ expert buffer type
//
// 這是一個「假」buffer type:它宣稱接受所有張量,但實際上
//   * 不配置任何 RAM(alloc_buffer 回傳 0 大小的 buffer)
//   * set_tensor 忽略傳入的資料(資料來自 llama.cpp 的 mmap,我們不碰)
//   * 真正的資料由 sdq::pager 從檔案 pread 到自己的 arena
//
// 用途:讓 llama.cpp 把 18.2 GiB 的 MoE 專家權重「掛在模型上」卻不放進 RAM,
// 改由分頁器管理 SSD → RAM → CPU 的三層資料流。

#include "sdq_pager.h"

#include "ggml-backend-impl.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <mutex>
#include <unordered_map>
#include <vector>

namespace sdq {

// 給 tensor->data 的假位址。每個 expert 權重張量分到一小塊(預設 4 KB),
// 指標必須有效(ggml 會做指標運算),但內容永遠不會被讀。
struct expert_buffer {
    std::vector<uint8_t> scratch;
};

static bool g_enabled = false;

void    sdq_set_enabled(bool on) { g_enabled = on; }
bool    sdq_enabled()           { return g_enabled; }

static config g_cfg;
void        sdq_set_config(const config & c) { g_cfg = c; }
const config & sdq_get_config() { return g_cfg; }

// ---------------------------------------------------------------- buffer iface

// ggml_backend_buffer_i 的欄位依賴下面的實作,先給宣告
struct ggml_backend_buffer_i sdq_buffer_iface();

static const char * sdq_buft_get_name(ggml_backend_buffer_type_t buft) {
    (void) buft;
    return "SDQ_ExpertSSD";
}

static size_t sdq_buft_get_alignment(ggml_backend_buffer_type_t buft) {
    (void) buft;
    return 4096;
}

static size_t sdq_buft_get_max_size(ggml_backend_buffer_type_t buft) {
    (void) buft;
    return SIZE_MAX;
}

// 每個 expert 權重張量只回報 4 KB:18.2 GiB 的權重「不會」被載入 RAM 的關鍵。
static size_t sdq_buft_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) {
    (void) buft;
    (void) tensor;
    return 4096;
}

static bool sdq_buft_is_host(ggml_backend_buffer_type_t buft) {
    (void) buft;
    return false;
}

static ggml_backend_buffer_t sdq_buft_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
    // llama.cpp 會用 get_alloc_size() 計算總大小後呼叫本函式;
    // 我們只保留真正需要的假位址空間(每個張量 4 KB)。
    const size_t bytes = size < 4096 ? 4096 : size;
    auto * buf = new expert_buffer();
    buf->scratch.assign(bytes, 0);
    auto * b = new ggml_backend_buffer{
        /* .iface   = */ sdq_buffer_iface(),
        /* .buft    = */ buft,
        /* .context = */ buf,
        /* .size    = */ bytes,
        /* .usage   = */ GGML_BACKEND_BUFFER_USAGE_WEIGHTS,
    };
    return b;
}

// 一次配置整個 context 的張量:每個張量 4 KB 假位址,實際權重 0 RAM。
static ggml_backend_buffer_t sdq_buft_alloc_buffer_n(ggml_backend_buffer_type_t buft,
                                                     ggml_tensor ** tensors, int n_tensors) {
    const size_t slot = 4096;
    const size_t bytes = slot * (size_t) std::max(n_tensors, 1);
    ggml_backend_buffer_t buffer = sdq_buft_alloc_buffer(buft, bytes);
    if (!buffer) {
        return nullptr;
    }
    uint8_t * base = (uint8_t *) buffer->iface.get_base(buffer);
    size_t off = 0;
    for (int i = 0; i < n_tensors; i++) {
        ggml_tensor * t = tensors[i];
        t->data = base + off;
        t->buffer = buffer;
        off += slot;
        buffer->iface.init_tensor(buffer, t);
    }
    return buffer;
}

static void sdq_buft_free_buffer(ggml_backend_buffer_t buffer) {
    // 只釋放自己的資料;ggml_backend_buffer_free() 會負責 delete buffer 本體,
    // 這裡再 delete 一次會造成 double free。
    delete (expert_buffer *) buffer->context;
    buffer->context = nullptr;
}

static void * sdq_buft_get_base(ggml_backend_buffer_t buffer) {
    return ((expert_buffer *) buffer->context)->scratch.data();
}

static ggml_status sdq_buft_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) {
    (void) buffer;
    (void) tensor;
    return GGML_STATUS_SUCCESS;
}

static void sdq_buft_memset_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor,
                                   uint8_t value, size_t offset, size_t size) {
    (void) buffer; (void) tensor; (void) value; (void) offset; (void) size;
}

static void sdq_buft_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor,
                                const void * data, size_t offset, size_t size) {
    // 關鍵:忽略。llama.cpp 會把 mmap 的資料交給我們,這裡一個位元組都不落地,
    // 專家權利永遠不會佔用 RAM。
    (void) buffer; (void) tensor; (void) data; (void) offset; (void) size;
}

static void sdq_buft_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor,
                                void * data, size_t offset, size_t size) {
    (void) buffer; (void) tensor; (void) offset;
    memset(data, 0, size);
}

static bool sdq_buft_cpy_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * src, ggml_tensor * dst) {
    (void) buffer; (void) src; (void) dst;
    return true;
}

static void sdq_buft_clear(ggml_backend_buffer_t buffer, uint8_t value) {
    (void) buffer; (void) value;
}

struct ggml_backend_buffer_i sdq_buffer_iface() {
    static ggml_backend_buffer_i i = [] {
        ggml_backend_buffer_i x {};
        x.free_buffer   = sdq_buft_free_buffer;
        x.get_base      = sdq_buft_get_base;
        x.init_tensor   = sdq_buft_init_tensor;
        x.memset_tensor = sdq_buft_memset_tensor;
        x.set_tensor    = sdq_buft_set_tensor;
        x.get_tensor    = sdq_buft_get_tensor;
        x.cpy_tensor    = sdq_buft_cpy_tensor;
        x.clear         = sdq_buft_clear;
        return x;
    }();
    return i;
}

ggml_backend_buffer_type_t sdq_expert_buft() {
    static ggml_backend_buffer_type_t buft = [] {
        return new ggml_backend_buffer_type{
            /* .iface   = */ {
                /* .get_name         = */ sdq_buft_get_name,
                /* .alloc_buffer     = */ sdq_buft_alloc_buffer,
                /* .alloc_buffer_n   = */ sdq_buft_alloc_buffer_n,
                /* .get_alignment    = */ sdq_buft_get_alignment,
                /* .get_max_size     = */ sdq_buft_get_max_size,
                /* .get_alloc_size   = */ sdq_buft_get_alloc_size,
                /* .get_alloc_size_n = */ nullptr,
                /* .is_host          = */ sdq_buft_is_host,
            },
            /* .device  = */ nullptr,
            /* .context = */ nullptr,
        };
    }();
    return buft;
}

// llama.cpp 端需要的介面宣告
void sdq_install_buft_overrides(llama_model_params * params);

static const llama_model_tensor_buft_override * sdq_overrides() {
    static llama_model_tensor_buft_override ov[] = {
        { "blk\\.[0-9]+\\.ffn_(gate|up|down|gate_up)_exps\\.weight", sdq_expert_buft() },
        { nullptr, nullptr },
    };
    return ov;
}

void sdq_install_buft_overrides(llama_model_params * params) {
    if (!params) {
        return;
    }
    if (params->tensor_buft_overrides == nullptr) {
        params->tensor_buft_overrides = sdq_overrides();
    } else {
        // 使用者已有 override:把手動指定的順序原樣保留,附加在尾端
        size_t n = 0;
        while (params->tensor_buft_overrides[n].pattern != nullptr) {
            n++;
        }
        static std::vector<llama_model_tensor_buft_override> merged;
        merged.clear();
        for (size_t i = 0; i < n; i++) {
            merged.push_back(params->tensor_buft_overrides[i]);
        }
        for (size_t i = 0; sdq_overrides()[i].pattern != nullptr; i++) {
            merged.push_back(sdq_overrides()[i]);
        }
        merged.push_back({ nullptr, nullptr });
        params->tensor_buft_overrides = merged.data();
    }
}

} // namespace sdq