llama: model_loader: add TENSOR_READ_LAZY (#27794)
* llama: model_loader: add TENSOR_GET_ROW_LAZY * add --tensor-read-lazy * rename to TENSOR_READ_LAZY * gen docs * address comments
This commit is contained in:
+59
-13
@@ -438,11 +438,34 @@ void llama_file::write_u32(uint32_t val) const { pimpl->write_u32(val); }
|
||||
|
||||
// llama_mmap
|
||||
|
||||
#if defined(_POSIX_MAPPED_FILES) || defined(_WIN32)
|
||||
// merge `ranges` and return their complement within [0, limit)
|
||||
static llama_mmap::ranges ranges_complement(llama_mmap::ranges ranges, size_t limit) {
|
||||
llama_mmap::ranges res;
|
||||
std::sort(ranges.begin(), ranges.end());
|
||||
|
||||
size_t pos = 0;
|
||||
for (const auto & range : ranges) {
|
||||
const size_t beg = std::min(range.first, limit);
|
||||
const size_t end = std::min(range.second, limit);
|
||||
if (beg > pos) {
|
||||
res.emplace_back(pos, beg);
|
||||
}
|
||||
pos = std::max(pos, end);
|
||||
}
|
||||
if (pos < limit) {
|
||||
res.emplace_back(pos, limit);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct llama_mmap::impl {
|
||||
#ifdef _POSIX_MAPPED_FILES
|
||||
std::vector<std::pair<size_t, size_t>> mapped_fragments;
|
||||
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
size = file->size();
|
||||
int fd = file->file_id();
|
||||
int flags = MAP_SHARED;
|
||||
@@ -452,18 +475,34 @@ struct llama_mmap::impl {
|
||||
LLAMA_LOG_WARN("warning: posix_fadvise(.., POSIX_FADV_SEQUENTIAL) failed: %s\n",
|
||||
strerror(errno));
|
||||
}
|
||||
if (prefetch) { flags |= MAP_POPULATE; }
|
||||
// MAP_POPULATE would fault in the lazy ranges too
|
||||
if (prefetch && lazy_ranges.empty()) { flags |= MAP_POPULATE; }
|
||||
#endif
|
||||
addr = mmap(NULL, file->size(), PROT_READ, flags, fd, 0);
|
||||
if (addr == MAP_FAILED) {
|
||||
throw std::runtime_error(format("mmap failed: %s", strerror(errno)));
|
||||
}
|
||||
|
||||
if (prefetch > 0) {
|
||||
if (posix_madvise(addr, std::min(file->size(), prefetch), POSIX_MADV_WILLNEED)) {
|
||||
LLAMA_LOG_WARN("warning: posix_madvise(.., POSIX_MADV_WILLNEED) failed: %s\n",
|
||||
strerror(errno));
|
||||
// page-aligned madvise over [beg, end), clamped to the file
|
||||
auto advise = [&](size_t beg, size_t end, int advice, const char * name) {
|
||||
const size_t page_size = sysconf(_SC_PAGESIZE);
|
||||
beg = beg & ~(page_size - 1);
|
||||
end = std::min((end + page_size - 1) & ~(page_size - 1), file->size());
|
||||
if (beg >= end) {
|
||||
return;
|
||||
}
|
||||
if (posix_madvise((char *) addr + beg, end - beg, advice)) {
|
||||
LLAMA_LOG_WARN("warning: posix_madvise(.., %s) failed: %s\n", name, strerror(errno));
|
||||
}
|
||||
};
|
||||
|
||||
if (prefetch > 0) {
|
||||
for (const auto & range : ranges_complement(lazy_ranges, std::min(file->size(), prefetch))) {
|
||||
advise(range.first, range.second, POSIX_MADV_WILLNEED, "POSIX_MADV_WILLNEED");
|
||||
}
|
||||
}
|
||||
for (const auto & range : lazy_ranges) {
|
||||
advise(range.first, range.second, POSIX_MADV_RANDOM, "POSIX_MADV_RANDOM");
|
||||
}
|
||||
if (numa) {
|
||||
if (posix_madvise(addr, file->size(), POSIX_MADV_RANDOM)) {
|
||||
@@ -533,7 +572,7 @@ struct llama_mmap::impl {
|
||||
#elif defined(_WIN32)
|
||||
HANDLE hMapping = nullptr;
|
||||
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
GGML_UNUSED(numa);
|
||||
|
||||
size = file->size();
|
||||
@@ -563,10 +602,15 @@ struct llama_mmap::impl {
|
||||
pPrefetchVirtualMemory = (decltype(pPrefetchVirtualMemory))(void *) GetProcAddress(hKernel32, "PrefetchVirtualMemory");
|
||||
|
||||
if (pPrefetchVirtualMemory) {
|
||||
WIN32_MEMORY_RANGE_ENTRY range;
|
||||
range.VirtualAddress = addr;
|
||||
range.NumberOfBytes = (SIZE_T) std::min(size, prefetch);
|
||||
if (!pPrefetchVirtualMemory(GetCurrentProcess(), 1, &range, 0)) {
|
||||
std::vector<WIN32_MEMORY_RANGE_ENTRY> entries;
|
||||
for (const auto & range : ranges_complement(lazy_ranges, std::min(size, prefetch))) {
|
||||
WIN32_MEMORY_RANGE_ENTRY entry;
|
||||
entry.VirtualAddress = (char *) addr + range.first;
|
||||
entry.NumberOfBytes = (SIZE_T) (range.second - range.first);
|
||||
entries.push_back(entry);
|
||||
}
|
||||
if (!entries.empty() &&
|
||||
!pPrefetchVirtualMemory(GetCurrentProcess(), (ULONG_PTR) entries.size(), entries.data(), 0)) {
|
||||
LLAMA_LOG_WARN("warning: PrefetchVirtualMemory failed: %s\n",
|
||||
llama_format_win_err(GetLastError()).c_str());
|
||||
}
|
||||
@@ -597,10 +641,11 @@ struct llama_mmap::impl {
|
||||
}
|
||||
}
|
||||
#else
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa) {
|
||||
impl(struct llama_file * file, size_t prefetch, bool numa, const llama_mmap::ranges & lazy_ranges) {
|
||||
GGML_UNUSED(file);
|
||||
GGML_UNUSED(prefetch);
|
||||
GGML_UNUSED(numa);
|
||||
GGML_UNUSED(lazy_ranges);
|
||||
|
||||
throw std::runtime_error("mmap not supported");
|
||||
}
|
||||
@@ -617,7 +662,8 @@ struct llama_mmap::impl {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
llama_mmap::llama_mmap(struct llama_file * file, size_t prefetch, bool numa) : pimpl(std::make_unique<impl>(file, prefetch, numa)) {}
|
||||
llama_mmap::llama_mmap(struct llama_file * file, size_t prefetch, bool numa,
|
||||
const ranges & lazy_ranges) : pimpl(std::make_unique<impl>(file, prefetch, numa, lazy_ranges)) {}
|
||||
llama_mmap::~llama_mmap() = default;
|
||||
|
||||
size_t llama_mmap::size() const { return pimpl->size; }
|
||||
|
||||
+6
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -41,8 +42,12 @@ private:
|
||||
};
|
||||
|
||||
struct llama_mmap {
|
||||
// list of [first, last) byte ranges within a file
|
||||
using ranges = std::vector<std::pair<size_t, size_t>>;
|
||||
|
||||
llama_mmap(const llama_mmap &) = delete;
|
||||
llama_mmap(struct llama_file * file, size_t prefetch = (size_t) -1, bool numa = false);
|
||||
llama_mmap(struct llama_file * file, size_t prefetch = (size_t) -1, bool numa = false,
|
||||
const ranges & lazy_ranges = {});
|
||||
~llama_mmap();
|
||||
|
||||
size_t size() const;
|
||||
|
||||
@@ -1282,6 +1282,18 @@ struct ggml_tensor * llama_model_loader::create_tensor(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((flags & TENSOR_READ_LAZY) && use_mmap && tensor_read_lazy != LLAMA_TENSOR_READ_LAZY_OFF) {
|
||||
// in auto mode, small tensors are cheap enough to keep resident
|
||||
constexpr size_t auto_lazy_min_size = 4ull * 1024 * 1024 * 1024;
|
||||
if (tensor_read_lazy == LLAMA_TENSOR_READ_LAZY_ON || ggml_nbytes(cur) > auto_lazy_min_size) {
|
||||
const auto & w = require_weight(tn.str().c_str());
|
||||
lazy_tensor_ranges[w.idx].emplace_back(w.offs, w.offs + ggml_nbytes(cur));
|
||||
|
||||
LLAMA_LOG_INFO("%s: tensor %s (size = %zu MiB) lazy read enabled\n",
|
||||
__func__, tn.str().c_str(), ggml_nbytes(cur)/1024/1024);
|
||||
}
|
||||
}
|
||||
|
||||
ggml_tensor t_meta = *cur;
|
||||
if (flags & TENSOR_ALLOW_RESHAPE) {
|
||||
for (size_t dim = 0; dim < GGML_MAX_DIMS; dim++) {
|
||||
@@ -1349,7 +1361,9 @@ void llama_model_loader::init_mappings(bool prefetch, llama_mlocks * mlock_mmaps
|
||||
if (use_mmap) {
|
||||
mappings.reserve(files.size());
|
||||
mmaps_used.reserve(files.size());
|
||||
for (const auto & file : files) {
|
||||
for (uint32_t idx = 0; idx < files.size(); idx++) {
|
||||
const auto & file = files[idx];
|
||||
|
||||
bool is_numa = false;
|
||||
|
||||
auto * dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
||||
@@ -1361,7 +1375,11 @@ void llama_model_loader::init_mappings(bool prefetch, llama_mlocks * mlock_mmaps
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<llama_mmap> mapping = std::make_unique<llama_mmap>(file.get(), prefetch ? -1 : 0, is_numa);
|
||||
const auto it_lazy = lazy_tensor_ranges.find(idx);
|
||||
static const llama_mmap::ranges no_lazy_ranges;
|
||||
|
||||
std::unique_ptr<llama_mmap> mapping = std::make_unique<llama_mmap>(file.get(), prefetch ? -1 : 0, is_numa,
|
||||
it_lazy != lazy_tensor_ranges.end() ? it_lazy->second : no_lazy_ranges);
|
||||
mmaps_used.emplace_back(mapping->size(), 0);
|
||||
if (mlock_mmaps) {
|
||||
std::unique_ptr<llama_mlock> mlock_mmap(new llama_mlock());
|
||||
|
||||
@@ -68,6 +68,7 @@ struct llama_model_loader {
|
||||
static const int TENSOR_SKIP = 1 << 2;
|
||||
static const int TENSOR_SKIP_IF_VIRTUAL = 1 << 3;
|
||||
static const int TENSOR_ALLOW_RESHAPE = 1 << 4;
|
||||
static const int TENSOR_READ_LAZY = 1 << 5; // read rows on demand instead of loading whole tensor; requires mmap for now
|
||||
|
||||
int n_kv = 0;
|
||||
int n_tensors = 0;
|
||||
@@ -82,12 +83,18 @@ struct llama_model_loader {
|
||||
bool no_alloc;
|
||||
bool load_mtp;
|
||||
|
||||
// set by the caller before the create_tensor() calls
|
||||
enum llama_tensor_read_lazy tensor_read_lazy = LLAMA_TENSOR_READ_LAZY_OFF;
|
||||
|
||||
llama_files files;
|
||||
llama_ftype ftype;
|
||||
llama_fver fver;
|
||||
|
||||
llama_mmaps mappings;
|
||||
|
||||
// byte ranges of TENSOR_READ_LAZY tensors, per file index
|
||||
std::map<uint32_t, llama_mmap::ranges> lazy_tensor_ranges;
|
||||
|
||||
std::map<std::string, llama_tensor_weight, weight_name_comparer> weights_map;
|
||||
std::unordered_map<std::string, llama_model_kv_override> kv_overrides;
|
||||
const llama_model_tensor_buft_override * tensor_buft_overrides;
|
||||
|
||||
+3
-1
@@ -2631,6 +2631,7 @@ llama_model_params llama_model_default_params() {
|
||||
/*.n_gpu_layers =*/ -1,
|
||||
/*.split_mode =*/ LLAMA_SPLIT_MODE_LAYER,
|
||||
/*.load_mode =*/ LLAMA_LOAD_MODE_AUTO,
|
||||
/*.tensor_read_lazy =*/ LLAMA_TENSOR_READ_LAZY_AUTO,
|
||||
/*.main_gpu =*/ 0,
|
||||
/*.tensor_split =*/ nullptr,
|
||||
/*.progress_callback =*/ nullptr,
|
||||
@@ -3067,7 +3068,8 @@ llama_model_base::llama_model_base(const struct llama_model_params & params) : l
|
||||
TENSOR_NOT_REQUIRED (llama_model_loader::TENSOR_NOT_REQUIRED),
|
||||
TENSOR_SKIP (llama_model_loader::TENSOR_SKIP),
|
||||
TENSOR_SKIP_IF_VIRTUAL(llama_model_loader::TENSOR_SKIP_IF_VIRTUAL),
|
||||
TENSOR_ALLOW_RESHAPE (llama_model_loader::TENSOR_ALLOW_RESHAPE) {}
|
||||
TENSOR_ALLOW_RESHAPE (llama_model_loader::TENSOR_ALLOW_RESHAPE),
|
||||
TENSOR_READ_LAZY (llama_model_loader::TENSOR_READ_LAZY) {}
|
||||
|
||||
ggml_tensor * llama_model_base::create_tensor(const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
|
||||
GGML_ASSERT(ml != nullptr);
|
||||
|
||||
@@ -756,6 +756,7 @@ struct llama_model_base : public llama_model {
|
||||
const int TENSOR_SKIP;
|
||||
const int TENSOR_SKIP_IF_VIRTUAL;
|
||||
const int TENSOR_ALLOW_RESHAPE;
|
||||
const int TENSOR_READ_LAZY;
|
||||
|
||||
explicit llama_model_base(const llama_model_params & params);
|
||||
virtual ~llama_model_base() = default;
|
||||
|
||||
@@ -318,6 +318,8 @@ static std::pair<int, llama_model *> llama_model_load(struct gguf_context * meta
|
||||
llama_model_loader ml(metadata, set_tensor_data, set_tensor_data_ud, fname, splits, file, params.load_mode,
|
||||
params.check_tensors, params.no_alloc, params.load_mtp, params.kv_overrides, params.tensor_buft_overrides);
|
||||
|
||||
ml.tensor_read_lazy = params.tensor_read_lazy;
|
||||
|
||||
ml.print_info();
|
||||
std::unique_ptr<llama_model> model_ptr(llama_model_create(ml, params));
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ void llama_model_gemma4::load_arch_tensors(llama_model_loader &) {
|
||||
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
|
||||
|
||||
if (n_embd_per_layer > 0) {
|
||||
per_layer_tok_embd = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"), {n_embd_per_layer * n_layer, n_vocab}, 0);
|
||||
per_layer_tok_embd = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"), {n_embd_per_layer * n_layer, n_vocab}, TENSOR_READ_LAZY);
|
||||
per_layer_model_proj = create_tensor(tn(LLM_TENSOR_PER_LAYER_MODEL_PROJ, "weight", 0), {n_embd, n_embd_per_layer * n_layer}, 0);
|
||||
per_layer_proj_norm = create_tensor(tn(LLM_TENSOR_PER_LAYER_PROJ_NORM, "weight", 0), {n_embd_per_layer}, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user