llama : add option to save memory in device buffers (#22679)
* llama : add option to save memory in device buffers * tests : extend llama-save-load-state
This commit is contained in:
+231
-39
@@ -2230,13 +2230,17 @@ llm_graph_cb llama_context::graph_get_cb() const {
|
||||
|
||||
class llama_io_write_dummy : public llama_io_write_i {
|
||||
public:
|
||||
llama_io_write_dummy() = default;
|
||||
llama_io_write_dummy(bool skip_tensors) : skip_tensors(skip_tensors) {}
|
||||
|
||||
void write(const void * /* src */, size_t size) override {
|
||||
size_written += size;
|
||||
}
|
||||
|
||||
void write_tensor(const ggml_tensor * /* tensor */, size_t /* offset */, size_t size) override {
|
||||
void write_tensor(ggml_tensor * /* tensor */, size_t /* offset */, size_t size) override {
|
||||
if (skip_tensors) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_written += size;
|
||||
}
|
||||
|
||||
@@ -2245,34 +2249,21 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const bool skip_tensors;
|
||||
|
||||
size_t size_written = 0;
|
||||
};
|
||||
|
||||
class llama_io_write_buffer : public llama_io_write_i {
|
||||
class llama_io_write_host : public llama_io_write_i {
|
||||
public:
|
||||
llama_io_write_buffer(
|
||||
llama_io_write_host(
|
||||
uint8_t * p, size_t len) : ptr(p), buf_size(len) {}
|
||||
|
||||
~llama_io_write_buffer() {
|
||||
#if 1
|
||||
~llama_io_write_host() {
|
||||
// TODO: add backend support to batch tensor_get? or some other way to speed this up
|
||||
for (const auto & info : winfos) {
|
||||
ggml_backend_tensor_get(info.tensor, info.ptr, info.offset, info.size);
|
||||
for (const auto & winfo : winfos) {
|
||||
ggml_backend_tensor_get(winfo.tensor, winfo.ptr, winfo.offset, winfo.size);
|
||||
}
|
||||
#else
|
||||
// flush the writes asynchronously
|
||||
// this helps on Macs, but on other devices - it does not. just an example
|
||||
std::vector<std::future<void>> futures;
|
||||
futures.reserve(winfos.size());
|
||||
for (const auto & info : winfos) {
|
||||
futures.push_back(std::async(std::launch::async, [info]() {
|
||||
ggml_backend_tensor_get(info.tensor, info.ptr, info.offset, info.size);
|
||||
}));
|
||||
}
|
||||
for (auto & f : futures) {
|
||||
f.wait();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void write(const void * src, size_t size) override {
|
||||
@@ -2285,7 +2276,7 @@ public:
|
||||
buf_size -= size;
|
||||
}
|
||||
|
||||
void write_tensor(const ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
void write_tensor(ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
if (size > buf_size) {
|
||||
throw std::runtime_error("unexpectedly reached end of buffer");
|
||||
}
|
||||
@@ -2308,7 +2299,7 @@ private:
|
||||
size_t size_written = 0;
|
||||
|
||||
struct write_info {
|
||||
const ggml_tensor * tensor;
|
||||
ggml_tensor * tensor;
|
||||
uint8_t * ptr;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
@@ -2316,14 +2307,14 @@ private:
|
||||
std::vector<write_info> winfos;
|
||||
};
|
||||
|
||||
class llama_io_read_buffer : public llama_io_read_i {
|
||||
class llama_io_read_host : public llama_io_read_i {
|
||||
public:
|
||||
llama_io_read_buffer(const uint8_t * p, size_t len) : ptr(p), buf_size(len) {}
|
||||
llama_io_read_host(const uint8_t * p, size_t len) : ptr(p), buf_size(len) {}
|
||||
|
||||
~llama_io_read_buffer() {
|
||||
~llama_io_read_host() {
|
||||
// flush the reads
|
||||
for (const auto & info : rinfos) {
|
||||
ggml_backend_tensor_set(info.tensor, info.ptr, info.offset, info.size);
|
||||
for (const auto & rinfo : rinfos) {
|
||||
ggml_backend_tensor_set(rinfo.tensor, rinfo.ptr, rinfo.offset, rinfo.size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2377,7 +2368,7 @@ public:
|
||||
size_written += size;
|
||||
}
|
||||
|
||||
void write_tensor(const ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
void write_tensor(ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
temp_buffer.resize(size);
|
||||
ggml_backend_tensor_get(tensor, temp_buffer.data(), offset, size);
|
||||
write(temp_buffer.data(), temp_buffer.size());
|
||||
@@ -2418,8 +2409,162 @@ private:
|
||||
std::vector<uint8_t> temp_buffer;
|
||||
};
|
||||
|
||||
class llama_io_write_device : public llama_io_write_i {
|
||||
public:
|
||||
llama_io_write_device(uint8_t * p, size_t len, llama_memory_buffers & mbufs) : ptr(p), buf_size(len), mbufs(mbufs) {
|
||||
}
|
||||
|
||||
~llama_io_write_device() {
|
||||
llama_memory_buffers mbufs_new;
|
||||
|
||||
for (const auto & winfo : winfos) {
|
||||
auto * buft = ggml_backend_buffer_get_type(winfo.tensor->buffer);
|
||||
|
||||
mbufs_new[buft].n_tensors++;
|
||||
mbufs_new[buft].total_size += winfo.size;
|
||||
}
|
||||
|
||||
for (auto & [buft, mbuf] : mbufs_new) {
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ 2*mbuf.n_tensors*ggml_tensor_overhead(),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
|
||||
mbuf.ctx.reset(ggml_init(params));
|
||||
|
||||
mbuf.org.reserve(mbuf.n_tensors);
|
||||
mbuf.cpy.reserve(mbuf.n_tensors);
|
||||
}
|
||||
|
||||
for (const auto & winfo : winfos) {
|
||||
auto * buft = ggml_backend_buffer_get_type(winfo.tensor->buffer);
|
||||
|
||||
const int64_t n = winfo.size/ggml_element_size(winfo.tensor);
|
||||
|
||||
auto & mbuf = mbufs_new[buft];
|
||||
|
||||
mbuf.org.push_back(ggml_view_1d (mbuf.ctx.get(), winfo.tensor, n, winfo.offset));
|
||||
mbuf.cpy.push_back(ggml_new_tensor_1d(mbuf.ctx.get(), winfo.tensor->type, n));
|
||||
}
|
||||
|
||||
for (auto & [buft, mbuf] : mbufs_new) {
|
||||
auto & mbuf_cur = mbufs[buft];
|
||||
|
||||
if (!mbuf_cur.buf || mbuf_cur.org.size() != mbuf.org.size() || mbuf_cur.total_size != mbuf.total_size) {
|
||||
mbuf_cur = std::move(mbuf);
|
||||
|
||||
mbuf_cur.buf.reset(ggml_backend_alloc_ctx_tensors_from_buft(mbuf_cur.ctx.get(), buft));
|
||||
|
||||
LLAMA_LOG_INFO("%s: allocated '%s' buffer %.3f MiB\n", __func__, ggml_backend_buft_name(buft), mbuf.total_size/1024.0/1024.0);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mbuf_cur.org.size(); ++i) {
|
||||
ggml_backend_tensor_copy(mbuf_cur.org[i], mbuf_cur.cpy[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(const void * src, size_t size) override {
|
||||
if (size > buf_size) {
|
||||
throw std::runtime_error("unexpectedly reached end of buffer");
|
||||
}
|
||||
memcpy(ptr, src, size);
|
||||
ptr += size;
|
||||
size_written += size;
|
||||
buf_size -= size;
|
||||
}
|
||||
|
||||
void write_tensor(ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
// save the write for later during destruction
|
||||
winfos.push_back({tensor, ptr, size, offset});
|
||||
}
|
||||
|
||||
size_t n_bytes() override {
|
||||
return size_written;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t * ptr;
|
||||
size_t buf_size = 0;
|
||||
size_t size_written = 0;
|
||||
|
||||
struct write_info {
|
||||
ggml_tensor * tensor;
|
||||
uint8_t * ptr;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
std::vector<write_info> winfos;
|
||||
|
||||
llama_memory_buffers & mbufs;
|
||||
};
|
||||
|
||||
class llama_io_read_device : public llama_io_read_i {
|
||||
public:
|
||||
llama_io_read_device(const uint8_t * p, size_t len, const llama_memory_buffers & mbufs) : ptr(p), buf_size(len), mbufs(mbufs) {
|
||||
}
|
||||
|
||||
~llama_io_read_device() {
|
||||
llama_memory_buffers mbufs_new;
|
||||
|
||||
for (const auto & rinfo : rinfos) {
|
||||
auto * buft = ggml_backend_buffer_get_type(rinfo.tensor->buffer);
|
||||
|
||||
mbufs_new[buft].n_tensors++;
|
||||
mbufs_new[buft].total_size += rinfo.size;
|
||||
}
|
||||
|
||||
for (auto & [buft, mbuf] : mbufs_new) {
|
||||
const auto & mbuf_cur = mbufs.at(buft);
|
||||
|
||||
if (!mbuf_cur.buf || mbuf_cur.n_tensors != mbuf.n_tensors || mbuf_cur.total_size != mbuf.total_size) {
|
||||
GGML_ABORT("%s: memory buffer mismatch\n", __func__);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mbuf_cur.org.size(); ++i) {
|
||||
ggml_backend_tensor_copy(mbuf_cur.cpy[i], mbuf_cur.org[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void read(void * dst, size_t size) override {
|
||||
if (size > buf_size) {
|
||||
throw std::runtime_error("unexpectedly reached end of buffer");
|
||||
}
|
||||
memcpy(dst, ptr, size);
|
||||
ptr += size;
|
||||
size_read += size;
|
||||
buf_size -= size;
|
||||
}
|
||||
|
||||
void read_tensor(ggml_tensor * tensor, size_t offset, size_t size) override {
|
||||
// save for later during destruction
|
||||
rinfos.push_back({tensor, ptr, size, offset});
|
||||
}
|
||||
|
||||
size_t n_bytes() override {
|
||||
return size_read;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t * ptr;
|
||||
size_t buf_size = 0;
|
||||
size_t size_read = 0;
|
||||
|
||||
struct read_info {
|
||||
ggml_tensor * tensor;
|
||||
const uint8_t * ptr;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
std::vector<read_info> rinfos;
|
||||
|
||||
const llama_memory_buffers & mbufs;
|
||||
};
|
||||
|
||||
size_t llama_context::state_get_size() {
|
||||
llama_io_write_dummy io;
|
||||
llama_io_write_dummy io(false);
|
||||
try {
|
||||
return state_write_data(io);
|
||||
} catch (const std::exception & err) {
|
||||
@@ -2429,7 +2574,7 @@ size_t llama_context::state_get_size() {
|
||||
}
|
||||
|
||||
size_t llama_context::state_get_data(uint8_t * dst, size_t size) {
|
||||
llama_io_write_buffer io(dst, size);
|
||||
llama_io_write_host io(dst, size);
|
||||
try {
|
||||
return state_write_data(io);
|
||||
} catch (const std::exception & err) {
|
||||
@@ -2439,7 +2584,7 @@ size_t llama_context::state_get_data(uint8_t * dst, size_t size) {
|
||||
}
|
||||
|
||||
size_t llama_context::state_set_data(const uint8_t * src, size_t size) {
|
||||
llama_io_read_buffer io(src, size);
|
||||
llama_io_read_host io(src, size);
|
||||
try {
|
||||
return state_read_data(io);
|
||||
} catch (const std::exception & err) {
|
||||
@@ -2448,9 +2593,14 @@ size_t llama_context::state_set_data(const uint8_t * src, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint32_t io_magic = 0xaf143cd8;
|
||||
|
||||
size_t llama_context::state_seq_get_size(llama_seq_id seq_id, llama_state_seq_flags flags) {
|
||||
llama_io_write_dummy io;
|
||||
llama_io_write_dummy io(flags & LLAMA_STATE_SEQ_FLAGS_ON_DEVICE);
|
||||
try {
|
||||
io.write(&io_magic, sizeof(io_magic));
|
||||
io.write(&seq_id, sizeof(seq_id));
|
||||
|
||||
return state_seq_write_data(io, seq_id, flags);
|
||||
} catch (const std::exception & err) {
|
||||
LLAMA_LOG_ERROR("%s: error getting state size: %s\n", __func__, err.what());
|
||||
@@ -2459,9 +2609,18 @@ size_t llama_context::state_seq_get_size(llama_seq_id seq_id, llama_state_seq_fl
|
||||
}
|
||||
|
||||
size_t llama_context::state_seq_get_data(llama_seq_id seq_id, uint8_t * dst, size_t size, llama_state_seq_flags flags) {
|
||||
llama_io_write_buffer io(dst, size);
|
||||
std::unique_ptr<llama_io_write_i> io;
|
||||
if (flags & LLAMA_STATE_SEQ_FLAGS_ON_DEVICE) {
|
||||
io = std::make_unique<llama_io_write_device>(dst, size, mem_storage[seq_id]);
|
||||
} else {
|
||||
io = std::make_unique<llama_io_write_host>(dst, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return state_seq_write_data(io, seq_id, flags);
|
||||
io->write(&io_magic, sizeof(io_magic));
|
||||
io->write(&seq_id, sizeof(seq_id));
|
||||
|
||||
return state_seq_write_data(*io, seq_id, flags);
|
||||
} catch (const std::exception & err) {
|
||||
LLAMA_LOG_ERROR("%s: error saving state: %s\n", __func__, err.what());
|
||||
return 0;
|
||||
@@ -2469,9 +2628,43 @@ size_t llama_context::state_seq_get_data(llama_seq_id seq_id, uint8_t * dst, siz
|
||||
}
|
||||
|
||||
size_t llama_context::state_seq_set_data(llama_seq_id seq_id, const uint8_t * src, size_t size, llama_state_seq_flags flags) {
|
||||
llama_io_read_buffer io(src, size);
|
||||
std::unique_ptr<llama_io_read_i> io;
|
||||
if (flags & LLAMA_STATE_SEQ_FLAGS_ON_DEVICE) {
|
||||
// create a temporary io to read the magic and the src seq_id
|
||||
io = std::make_unique<llama_io_read_host>(src, size);
|
||||
|
||||
uint32_t magic_read;
|
||||
io->read(&magic_read, sizeof(magic_read));
|
||||
if (io_magic != magic_read) {
|
||||
throw std::runtime_error("wrong sequence state magic");
|
||||
}
|
||||
|
||||
llama_seq_id seq_id_read;
|
||||
io->read(&seq_id_read, sizeof(seq_id_read));
|
||||
|
||||
GGML_ASSERT(mem_storage.find(seq_id_read) != mem_storage.end());
|
||||
|
||||
io = std::make_unique<llama_io_read_device>(src, size, mem_storage[seq_id_read]);
|
||||
} else {
|
||||
io = std::make_unique<llama_io_read_host>(src, size);
|
||||
}
|
||||
|
||||
try {
|
||||
return state_seq_read_data(io, seq_id, flags);
|
||||
uint32_t magic_read;
|
||||
io->read(&magic_read, sizeof(magic_read));
|
||||
if (io_magic != magic_read) {
|
||||
throw std::runtime_error("wrong sequence state magic");
|
||||
}
|
||||
|
||||
const bool need_seq_match = (flags & LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
|
||||
llama_seq_id seq_id_read;
|
||||
io->read(&seq_id_read, sizeof(seq_id_read));
|
||||
if (need_seq_match && seq_id != seq_id_read) {
|
||||
throw std::runtime_error("wrong sequence id");
|
||||
}
|
||||
|
||||
return state_seq_read_data(*io, seq_id, flags);
|
||||
} catch (const std::exception & err) {
|
||||
LLAMA_LOG_ERROR("%s: error loading state: %s\n", __func__, err.what());
|
||||
return 0;
|
||||
@@ -3462,7 +3655,6 @@ size_t llama_state_seq_get_data_ext(llama_context * ctx, uint8_t * dst, size_t s
|
||||
|
||||
return ctx->state_seq_get_data(seq_id, dst, size, flags);
|
||||
}
|
||||
|
||||
size_t llama_state_seq_set_data_ext(llama_context * ctx, const uint8_t * src, size_t size, llama_seq_id seq_id, llama_state_seq_flags flags) {
|
||||
ctx->synchronize();
|
||||
|
||||
|
||||
@@ -23,6 +23,21 @@ class llama_io_write_i;
|
||||
struct llama_memory_i;
|
||||
struct llama_memory_context_i;
|
||||
|
||||
// stores copy of the memory in device buffer. used for fast state save/load
|
||||
struct llama_memory_buffer {
|
||||
int n_tensors = 0;
|
||||
size_t total_size = 0;
|
||||
|
||||
ggml_backend_buffer_ptr buf;
|
||||
|
||||
ggml_context_ptr ctx;
|
||||
|
||||
std::vector<ggml_tensor *> org;
|
||||
std::vector<ggml_tensor *> cpy;
|
||||
};
|
||||
|
||||
using llama_memory_buffers = std::map<ggml_backend_buffer_type_t, llama_memory_buffer>;
|
||||
|
||||
struct llama_context {
|
||||
// init scheduler and compute buffers, reserve worst-case graphs
|
||||
llama_context(
|
||||
@@ -128,6 +143,7 @@ struct llama_context {
|
||||
size_t state_set_data(const uint8_t * src, size_t size);
|
||||
|
||||
size_t state_seq_get_size(llama_seq_id seq_id, llama_state_seq_flags flags);
|
||||
|
||||
size_t state_seq_get_data(llama_seq_id seq_id, uint8_t * dst, size_t size, llama_state_seq_flags flags);
|
||||
size_t state_seq_set_data(llama_seq_id seq_id, const uint8_t * src, size_t size, llama_state_seq_flags flags);
|
||||
|
||||
@@ -328,6 +344,9 @@ private:
|
||||
// host buffer for the model output (logits and embeddings)
|
||||
ggml_backend_buffer_ptr buf_output;
|
||||
|
||||
// keep copies of the per-sequence memory on the device
|
||||
std::map<llama_seq_id, llama_memory_buffers> mem_storage;
|
||||
|
||||
bool has_evaluated_once = false;
|
||||
|
||||
// env: LLAMA_GRAPH_REUSE_DISABLE
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ public:
|
||||
virtual ~llama_io_write_i() = default;
|
||||
|
||||
virtual void write(const void * src, size_t size) = 0;
|
||||
virtual void write_tensor(const ggml_tensor * tensor, size_t offset, size_t size) = 0;
|
||||
virtual void write_tensor(ggml_tensor * tensor, size_t offset, size_t size) = 0;
|
||||
|
||||
// bytes written so far
|
||||
virtual size_t n_bytes() = 0;
|
||||
|
||||
@@ -784,7 +784,7 @@ void llama_memory_recurrent::state_write_data(llama_io_write_i & io, const std::
|
||||
const uint32_t n_layer = hparams.n_layer;
|
||||
|
||||
io.write(&s_trans, sizeof(s_trans));
|
||||
io.write(&n_layer, sizeof(n_layer));
|
||||
io.write(&n_layer, sizeof(n_layer));
|
||||
|
||||
// Iterate and write all the R tensors first, each row is a cell
|
||||
// Get whole range at a time
|
||||
|
||||
Reference in New Issue
Block a user