Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d080ba3d65 |
+37
-2
@@ -16,6 +16,41 @@ llama-cli \
|
|||||||
|
|
||||||
`--longhaul-cache` is the expert cache budget in GiB. It is required when `--longhaul` is used. The same options are accepted by `llama-server`.
|
`--longhaul-cache` is the expert cache budget in GiB. It is required when `--longhaul` is used. The same options are accepted by `llama-server`.
|
||||||
|
|
||||||
|
## RPC mode
|
||||||
|
|
||||||
|
RPC longhaul keeps the paging loop on the accelerator host so expert payloads do
|
||||||
|
not cross the network during generation. Build both hosts with `-DGGML_RPC=ON`.
|
||||||
|
Place the same GGUF shard files on both hosts, preserving their basenames, then
|
||||||
|
start the Metal host with the directory containing those shards:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ggml-rpc-server \
|
||||||
|
--device MTL0 \
|
||||||
|
--longhaul-root /path/to/model-directory \
|
||||||
|
--host 192.168.1.20
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the client normally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
llama-server \
|
||||||
|
--model /local/path/model.gguf \
|
||||||
|
--rpc 192.168.1.20:50052 \
|
||||||
|
--longhaul \
|
||||||
|
--longhaul-cache 2 \
|
||||||
|
--n-gpu-layers 99
|
||||||
|
```
|
||||||
|
|
||||||
|
RPC longhaul currently requires all repeating layers on one remote Metal device.
|
||||||
|
The server validates each shard basename, size, GGUF metadata layout digest, and
|
||||||
|
every registered expert range before decoding. An older server, a server without
|
||||||
|
`--longhaul-root`, or a shard mismatch is a fatal model-load error; the client
|
||||||
|
does not fall back to sending expert slices over RPC.
|
||||||
|
|
||||||
|
`--longhaul-root` grants connected RPC clients read access to registered byte
|
||||||
|
ranges in files in that directory. The RPC server remains experimental and
|
||||||
|
insecure and must not be exposed to an untrusted network.
|
||||||
|
|
||||||
Longhaul may reduce `--ubatch-size` so that every expert selected by one graph segment can be present in the cache at the same time. The effective value is logged during context creation. If one token selects more experts than the cache has slots, the routed MoE computation is split into multiple stages and the partial results are summed. This permits smaller caches at the cost of additional graph work.
|
Longhaul may reduce `--ubatch-size` so that every expert selected by one graph segment can be present in the cache at the same time. The effective value is logged during context creation. If one token selects more experts than the cache has slots, the routed MoE computation is split into multiple stages and the partial results are summed. This permits smaller caches at the cost of additional graph work.
|
||||||
|
|
||||||
The normal startup warmup is skipped automatically in longhaul mode. Routed expert weights are not read until the first real decode.
|
The normal startup warmup is skipped automatically in longhaul mode. Routed expert weights are not read until the first real decode.
|
||||||
@@ -24,9 +59,9 @@ The normal startup warmup is skipped automatically in longhaul mode. Routed expe
|
|||||||
|
|
||||||
Longhaul currently requires:
|
Longhaul currently requires:
|
||||||
|
|
||||||
- macOS with the Metal backend
|
- the Metal backend, either local on macOS or on one RPC host
|
||||||
- Qwen3.5 MoE or Laguna architecture
|
- Qwen3.5 MoE or Laguna architecture
|
||||||
- all repeating model layers assigned to Metal
|
- all repeating model layers assigned to local Metal or one RPC Metal device
|
||||||
- text generation without embeddings or LoRA adapters
|
- text generation without embeddings or LoRA adapters
|
||||||
|
|
||||||
Longhaul does not restrict the GGUF quantization type. Individual tensor types must still be supported by Metal.
|
Longhaul does not restrict the GGUF quantization type. Individual tensor types must still be supported by Metal.
|
||||||
|
|||||||
+44
-1
@@ -8,7 +8,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define RPC_PROTO_MAJOR_VERSION 4
|
#define RPC_PROTO_MAJOR_VERSION 4
|
||||||
#define RPC_PROTO_MINOR_VERSION 0
|
#define RPC_PROTO_MINOR_VERSION 0
|
||||||
#define RPC_PROTO_PATCH_VERSION 3
|
#define RPC_PROTO_PATCH_VERSION 4
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
|
static_assert(GGML_OP_COUNT == 101, "GGML_OP_COUNT has changed - update RPC_PROTO_PATCH_VERSION");
|
||||||
@@ -27,6 +27,49 @@ GGML_BACKEND_API void ggml_backend_rpc_get_device_memory(const char * endpoint,
|
|||||||
GGML_BACKEND_API void ggml_backend_rpc_start_server(const char * endpoint, const char * cache_dir,
|
GGML_BACKEND_API void ggml_backend_rpc_start_server(const char * endpoint, const char * cache_dir,
|
||||||
size_t n_threads, size_t n_devices, ggml_backend_dev_t * devices);
|
size_t n_threads, size_t n_devices, ggml_backend_dev_t * devices);
|
||||||
|
|
||||||
|
struct ggml_backend_rpc_longhaul_shard {
|
||||||
|
const char * name;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t metadata_size;
|
||||||
|
uint8_t metadata_sha256[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ggml_backend_rpc_longhaul_source {
|
||||||
|
struct ggml_tensor * tensor;
|
||||||
|
uint32_t shard;
|
||||||
|
uint64_t offset;
|
||||||
|
uint64_t expert_size;
|
||||||
|
int32_t layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ggml_backend_rpc_longhaul_params {
|
||||||
|
uint32_t n_layers;
|
||||||
|
uint32_t n_experts;
|
||||||
|
uint32_t n_slots;
|
||||||
|
size_t n_shards;
|
||||||
|
const struct ggml_backend_rpc_longhaul_shard * shards;
|
||||||
|
size_t n_sources;
|
||||||
|
const struct ggml_backend_rpc_longhaul_source * sources;
|
||||||
|
};
|
||||||
|
|
||||||
|
GGML_BACKEND_API bool ggml_backend_rpc_longhaul_register(
|
||||||
|
const struct ggml_backend_rpc_longhaul_params * params,
|
||||||
|
uint64_t * registration_id,
|
||||||
|
char * error,
|
||||||
|
size_t error_size);
|
||||||
|
|
||||||
|
GGML_BACKEND_API void ggml_backend_rpc_longhaul_unregister(
|
||||||
|
struct ggml_tensor * tensor,
|
||||||
|
uint64_t registration_id);
|
||||||
|
|
||||||
|
GGML_BACKEND_API void ggml_backend_rpc_start_server_with_options(
|
||||||
|
const char * endpoint,
|
||||||
|
const char * cache_dir,
|
||||||
|
const char * longhaul_root,
|
||||||
|
size_t n_threads,
|
||||||
|
size_t n_devices,
|
||||||
|
ggml_backend_dev_t * devices);
|
||||||
|
|
||||||
GGML_BACKEND_API ggml_backend_reg_t ggml_backend_rpc_reg(void);
|
GGML_BACKEND_API ggml_backend_reg_t ggml_backend_rpc_reg(void);
|
||||||
GGML_BACKEND_API ggml_backend_reg_t ggml_backend_rpc_add_server(const char * endpoint);
|
GGML_BACKEND_API ggml_backend_reg_t ggml_backend_rpc_add_server(const char * endpoint);
|
||||||
|
|
||||||
|
|||||||
+998
-14
File diff suppressed because it is too large
Load Diff
@@ -1359,10 +1359,12 @@ llm_graph_result * llama_context::process_ubatch(const llama_ubatch & ubatch, ll
|
|||||||
res->reset();
|
res->reset();
|
||||||
|
|
||||||
ggml_backend_sched_reset(sched.get());
|
ggml_backend_sched_reset(sched.get());
|
||||||
|
auto * longhaul = model.longhaul_cache();
|
||||||
|
const bool local_longhaul = longhaul && !longhaul->is_remote();
|
||||||
ggml_backend_sched_set_eval_callback(
|
ggml_backend_sched_set_eval_callback(
|
||||||
sched.get(),
|
sched.get(),
|
||||||
model.longhaul_cache() ? longhaul_eval_callback : cparams.cb_eval,
|
local_longhaul ? longhaul_eval_callback : cparams.cb_eval,
|
||||||
model.longhaul_cache() ? this : cparams.cb_eval_user_data);
|
local_longhaul ? this : cparams.cb_eval_user_data);
|
||||||
|
|
||||||
//const auto t_start_us = ggml_time_us();
|
//const auto t_start_us = ggml_time_us();
|
||||||
|
|
||||||
@@ -2461,7 +2463,7 @@ llm_graph_params llama_context::graph_params(
|
|||||||
bool llama_context::longhaul_eval_callback(ggml_tensor * tensor, bool ask, void * user_data) {
|
bool llama_context::longhaul_eval_callback(ggml_tensor * tensor, bool ask, void * user_data) {
|
||||||
auto * ctx = static_cast<llama_context *>(user_data);
|
auto * ctx = static_cast<llama_context *>(user_data);
|
||||||
auto * cache = ctx->model.longhaul_cache();
|
auto * cache = ctx->model.longhaul_cache();
|
||||||
GGML_ASSERT(cache != nullptr);
|
GGML_ASSERT(cache != nullptr && !cache->is_remote());
|
||||||
|
|
||||||
int layer = -1;
|
int layer = -1;
|
||||||
const bool remap = sscanf(tensor->name, "longhaul.remap.%d", &layer) == 1;
|
const bool remap = sscanf(tensor->name, "longhaul.remap.%d", &layer) == 1;
|
||||||
|
|||||||
+102
-2
@@ -1,23 +1,108 @@
|
|||||||
#include "llama-longhaul.h"
|
#include "llama-longhaul.h"
|
||||||
|
|
||||||
#include "llama-impl.h"
|
#include "llama-impl.h"
|
||||||
|
#include "llama-token-cache.h"
|
||||||
|
|
||||||
|
#include "ggml-rpc.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
llama_longhaul_cache::llama_longhaul_cache(
|
llama_longhaul_cache::llama_longhaul_cache(
|
||||||
llama_files files,
|
llama_files files,
|
||||||
std::vector<llama_model_loader::longhaul_source> sources,
|
std::vector<llama_model_loader::longhaul_source> sources,
|
||||||
|
std::vector<llama_model_loader::longhaul_shard> shards,
|
||||||
size_t n_slots,
|
size_t n_slots,
|
||||||
uint32_t n_experts,
|
uint32_t n_experts,
|
||||||
uint32_t n_layers) :
|
uint32_t n_layers,
|
||||||
|
bool remote) :
|
||||||
files(std::move(files)),
|
files(std::move(files)),
|
||||||
sources(std::move(sources)),
|
sources(std::move(sources)),
|
||||||
|
shards(std::move(shards)),
|
||||||
sources_by_layer(n_layers),
|
sources_by_layer(n_layers),
|
||||||
layers(n_layers),
|
layers(n_layers),
|
||||||
n_slots(n_slots),
|
n_slots(n_slots),
|
||||||
n_experts(n_experts) {
|
n_experts(n_experts),
|
||||||
|
remote(remote) {
|
||||||
|
if (remote) {
|
||||||
|
if (this->shards.size() != this->files.size() || this->sources.empty()) {
|
||||||
|
throw std::runtime_error("RPC longhaul requires named GGUF shards");
|
||||||
|
}
|
||||||
|
std::vector<std::array<uint8_t, 32>> digests(this->shards.size());
|
||||||
|
std::vector<ggml_backend_rpc_longhaul_shard> rpc_shards(this->shards.size());
|
||||||
|
for (size_t i = 0; i < this->shards.size(); ++i) {
|
||||||
|
const auto & shard = this->shards[i];
|
||||||
|
if (shard.metadata_size > shard.size) {
|
||||||
|
throw std::runtime_error("invalid RPC longhaul shard metadata range");
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> metadata(shard.metadata_size);
|
||||||
|
if (!metadata.empty()) {
|
||||||
|
this->files[i]->read_at(metadata.data(), metadata.size(), 0);
|
||||||
|
}
|
||||||
|
const std::string hex = llama_token_cache_hash(metadata.data(), metadata.size());
|
||||||
|
if (hex.size() != 64) {
|
||||||
|
throw std::runtime_error("failed to fingerprint RPC longhaul shard");
|
||||||
|
}
|
||||||
|
for (size_t j = 0; j < digests[i].size(); ++j) {
|
||||||
|
auto nibble = [](char c) -> uint8_t {
|
||||||
|
if (c >= '0' && c <= '9') return c - '0';
|
||||||
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||||
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||||
|
return 0xff;
|
||||||
|
};
|
||||||
|
const uint8_t hi = nibble(hex[2*j]);
|
||||||
|
const uint8_t lo = nibble(hex[2*j + 1]);
|
||||||
|
if (hi > 0x0f || lo > 0x0f) {
|
||||||
|
throw std::runtime_error("invalid RPC longhaul shard fingerprint");
|
||||||
|
}
|
||||||
|
digests[i][j] = (hi << 4) | lo;
|
||||||
|
}
|
||||||
|
rpc_shards[i] = {
|
||||||
|
shard.name.c_str(),
|
||||||
|
shard.size,
|
||||||
|
shard.metadata_size,
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
memcpy(rpc_shards[i].metadata_sha256, digests[i].data(), digests[i].size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ggml_backend_rpc_longhaul_source> rpc_sources;
|
||||||
|
rpc_sources.reserve(this->sources.size());
|
||||||
|
for (const auto & source : this->sources) {
|
||||||
|
rpc_sources.push_back({
|
||||||
|
source.tensor,
|
||||||
|
source.file_idx,
|
||||||
|
source.offset,
|
||||||
|
source.expert_size,
|
||||||
|
source.layer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ggml_backend_rpc_longhaul_params params = {
|
||||||
|
n_layers,
|
||||||
|
n_experts,
|
||||||
|
(uint32_t) n_slots,
|
||||||
|
rpc_shards.size(),
|
||||||
|
rpc_shards.data(),
|
||||||
|
rpc_sources.size(),
|
||||||
|
rpc_sources.data(),
|
||||||
|
};
|
||||||
|
ggml_backend_reg_t reg = ggml_backend_reg_by_name("RPC");
|
||||||
|
auto register_fn = reg ? (decltype(ggml_backend_rpc_longhaul_register) *)
|
||||||
|
ggml_backend_reg_get_proc_address(reg, "ggml_backend_rpc_longhaul_register") : nullptr;
|
||||||
|
if (!register_fn) {
|
||||||
|
throw std::runtime_error("RPC backend does not expose longhaul registration");
|
||||||
|
}
|
||||||
|
char error[256] = {};
|
||||||
|
if (!register_fn(¶ms, &remote_registration_id, error, sizeof(error))) {
|
||||||
|
throw std::runtime_error(error[0] ? error : "RPC longhaul registration failed");
|
||||||
|
}
|
||||||
|
this->files.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto & file : this->files) {
|
for (auto & file : this->files) {
|
||||||
file->set_no_cache();
|
file->set_no_cache();
|
||||||
}
|
}
|
||||||
@@ -45,6 +130,17 @@ llama_longhaul_cache::llama_longhaul_cache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
llama_longhaul_cache::~llama_longhaul_cache() {
|
llama_longhaul_cache::~llama_longhaul_cache() {
|
||||||
|
if (remote) {
|
||||||
|
if (!sources.empty() && remote_registration_id != 0) {
|
||||||
|
ggml_backend_reg_t reg = ggml_backend_reg_by_name("RPC");
|
||||||
|
auto unregister_fn = reg ? (decltype(ggml_backend_rpc_longhaul_unregister) *)
|
||||||
|
ggml_backend_reg_get_proc_address(reg, "ggml_backend_rpc_longhaul_unregister") : nullptr;
|
||||||
|
if (unregister_fn) {
|
||||||
|
unregister_fn(sources.front().tensor, remote_registration_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(io_mutex);
|
std::lock_guard<std::mutex> lock(io_mutex);
|
||||||
io_stopping = true;
|
io_stopping = true;
|
||||||
@@ -321,3 +417,7 @@ uint64_t llama_longhaul_cache::misses() const {
|
|||||||
uint64_t llama_longhaul_cache::bytes_read_count() const {
|
uint64_t llama_longhaul_cache::bytes_read_count() const {
|
||||||
return bytes_read;
|
return bytes_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool llama_longhaul_cache::is_remote() const {
|
||||||
|
return remote;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ struct llama_longhaul_cache {
|
|||||||
llama_longhaul_cache(
|
llama_longhaul_cache(
|
||||||
llama_files files,
|
llama_files files,
|
||||||
std::vector<llama_model_loader::longhaul_source> sources,
|
std::vector<llama_model_loader::longhaul_source> sources,
|
||||||
|
std::vector<llama_model_loader::longhaul_shard> shards,
|
||||||
size_t n_slots,
|
size_t n_slots,
|
||||||
uint32_t n_experts,
|
uint32_t n_experts,
|
||||||
uint32_t n_layers);
|
uint32_t n_layers,
|
||||||
|
bool remote);
|
||||||
~llama_longhaul_cache();
|
~llama_longhaul_cache();
|
||||||
|
|
||||||
bool remap(int layer, ggml_tensor * ids);
|
bool remap(int layer, ggml_tensor * ids);
|
||||||
@@ -31,6 +33,7 @@ struct llama_longhaul_cache {
|
|||||||
bool failed() const;
|
bool failed() const;
|
||||||
uint64_t misses() const;
|
uint64_t misses() const;
|
||||||
uint64_t bytes_read_count() const;
|
uint64_t bytes_read_count() const;
|
||||||
|
bool is_remote() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct layer_state {
|
struct layer_state {
|
||||||
@@ -49,6 +52,7 @@ private:
|
|||||||
|
|
||||||
llama_files files;
|
llama_files files;
|
||||||
std::vector<llama_model_loader::longhaul_source> sources;
|
std::vector<llama_model_loader::longhaul_source> sources;
|
||||||
|
std::vector<llama_model_loader::longhaul_shard> shards;
|
||||||
std::vector<std::vector<const llama_model_loader::longhaul_source *>> sources_by_layer;
|
std::vector<std::vector<const llama_model_loader::longhaul_source *>> sources_by_layer;
|
||||||
std::vector<layer_state> layers;
|
std::vector<layer_state> layers;
|
||||||
size_t n_slots;
|
size_t n_slots;
|
||||||
@@ -84,6 +88,8 @@ private:
|
|||||||
std::condition_variable io_done;
|
std::condition_variable io_done;
|
||||||
size_t io_pending = 0;
|
size_t io_pending = 0;
|
||||||
bool io_stopping = false;
|
bool io_stopping = false;
|
||||||
|
bool remote = false;
|
||||||
|
uint64_t remote_registration_id = 0;
|
||||||
|
|
||||||
bool source_is_direct(const llama_model_loader::longhaul_source & source) const;
|
bool source_is_direct(const llama_model_loader::longhaul_source & source) const;
|
||||||
bool load_plan_sources();
|
bool load_plan_sources();
|
||||||
|
|||||||
@@ -594,6 +594,11 @@ llama_model_loader::llama_model_loader(
|
|||||||
|
|
||||||
files.emplace_back(new llama_file(fname.c_str(), "rb", use_direct_io));
|
files.emplace_back(new llama_file(fname.c_str(), "rb", use_direct_io));
|
||||||
contexts.emplace_back(ctx);
|
contexts.emplace_back(ctx);
|
||||||
|
longhaul_shards.push_back({
|
||||||
|
std::filesystem::path(fname).filename().string(),
|
||||||
|
files.back()->size(),
|
||||||
|
gguf_get_data_offset(metadata),
|
||||||
|
});
|
||||||
|
|
||||||
// Save tensors data offset of the main file.
|
// Save tensors data offset of the main file.
|
||||||
// For subsidiary files, `meta` tensor data offset must not be used,
|
// For subsidiary files, `meta` tensor data offset must not be used,
|
||||||
@@ -662,6 +667,11 @@ llama_model_loader::llama_model_loader(
|
|||||||
|
|
||||||
files.emplace_back(new llama_file(fname_split, "rb", use_direct_io));
|
files.emplace_back(new llama_file(fname_split, "rb", use_direct_io));
|
||||||
contexts.emplace_back(ctx);
|
contexts.emplace_back(ctx);
|
||||||
|
longhaul_shards.push_back({
|
||||||
|
std::filesystem::path(fname_split).filename().string(),
|
||||||
|
files.back()->size(),
|
||||||
|
gguf_get_data_offset(ctx_gguf.get()),
|
||||||
|
});
|
||||||
|
|
||||||
// Save tensors data offset info of the shard.
|
// Save tensors data offset info of the shard.
|
||||||
for (ggml_tensor * cur = ggml_get_first_tensor(ctx); cur; cur = ggml_get_next_tensor(ctx, cur)) {
|
for (ggml_tensor * cur = ggml_get_first_tensor(ctx); cur; cur = ggml_get_next_tensor(ctx, cur)) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "ggml-cpp.h"
|
#include "ggml-cpp.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -77,6 +78,12 @@ struct llama_model_loader {
|
|||||||
int layer;
|
int layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct longhaul_shard {
|
||||||
|
std::string name;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t metadata_size;
|
||||||
|
};
|
||||||
|
|
||||||
int n_kv = 0;
|
int n_kv = 0;
|
||||||
int n_tensors = 0;
|
int n_tensors = 0;
|
||||||
int n_created = 0;
|
int n_created = 0;
|
||||||
@@ -115,6 +122,7 @@ struct llama_model_loader {
|
|||||||
std::vector<std::pair<size_t, size_t>> mmaps_used;
|
std::vector<std::pair<size_t, size_t>> mmaps_used;
|
||||||
size_t longhaul_slots = 0;
|
size_t longhaul_slots = 0;
|
||||||
std::vector<longhaul_source> longhaul_sources;
|
std::vector<longhaul_source> longhaul_sources;
|
||||||
|
std::vector<longhaul_shard> longhaul_shards;
|
||||||
|
|
||||||
// define a comparator for the buft -> ctx map to ensure that the order is well-defined:
|
// define a comparator for the buft -> ctx map to ensure that the order is well-defined:
|
||||||
struct ggml_backend_buft_comparator {
|
struct ggml_backend_buft_comparator {
|
||||||
|
|||||||
+29
-7
@@ -1039,6 +1039,7 @@ struct llama_model::impl {
|
|||||||
|
|
||||||
std::vector<float> tensor_split_owned;
|
std::vector<float> tensor_split_owned;
|
||||||
std::unique_ptr<llama_longhaul_cache> longhaul;
|
std::unique_ptr<llama_longhaul_cache> longhaul;
|
||||||
|
bool longhaul_remote = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
llama_model::llama_model(const llama_model_params & params) : params(params), pimpl(std::make_unique<impl>()) {
|
llama_model::llama_model(const llama_model_params & params) : params(params), pimpl(std::make_unique<impl>()) {
|
||||||
@@ -1356,9 +1357,6 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.load_mode == LLAMA_LOAD_MODE_LONGHAUL) {
|
if (params.load_mode == LLAMA_LOAD_MODE_LONGHAUL) {
|
||||||
#if !defined(__APPLE__)
|
|
||||||
throw std::runtime_error("longhaul is only supported on macOS");
|
|
||||||
#endif
|
|
||||||
if (arch != LLM_ARCH_QWEN35MOE && arch != LLM_ARCH_LAGUNA) {
|
if (arch != LLM_ARCH_QWEN35MOE && arch != LLM_ARCH_LAGUNA) {
|
||||||
throw std::runtime_error("longhaul currently requires a qwen35moe or laguna model");
|
throw std::runtime_error("longhaul currently requires a qwen35moe or laguna model");
|
||||||
}
|
}
|
||||||
@@ -1368,12 +1366,35 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
|||||||
if (params.check_tensors || params.no_alloc || params.vocab_only) {
|
if (params.check_tensors || params.no_alloc || params.vocab_only) {
|
||||||
throw std::runtime_error("longhaul does not support check-tensors, no-alloc, or vocab-only loading");
|
throw std::runtime_error("longhaul does not support check-tensors, no-alloc, or vocab-only loading");
|
||||||
}
|
}
|
||||||
|
bool all_metal = true;
|
||||||
|
bool all_rpc = true;
|
||||||
for (int il = 0; il < n_layer_all; ++il) {
|
for (int il = 0; il < n_layer_all; ++il) {
|
||||||
ggml_backend_dev_t dev = pimpl->dev_layer[il].dev;
|
ggml_backend_dev_t dev = pimpl->dev_layer[il].dev;
|
||||||
if (ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_GPU ||
|
const char * reg_name = ggml_backend_reg_name(ggml_backend_dev_backend_reg(dev));
|
||||||
strcmp(ggml_backend_reg_name(ggml_backend_dev_backend_reg(dev)), "MTL") != 0) {
|
all_metal = all_metal &&
|
||||||
throw std::runtime_error("longhaul requires every model layer on Metal");
|
ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_GPU &&
|
||||||
|
strcmp(reg_name, "MTL") == 0;
|
||||||
|
all_rpc = all_rpc &&
|
||||||
|
ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_GPU &&
|
||||||
|
strcmp(reg_name, "RPC") == 0;
|
||||||
|
}
|
||||||
|
if (!all_metal && !all_rpc) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"longhaul requires all model layers on local Metal or one RPC device");
|
||||||
|
}
|
||||||
|
if (all_metal) {
|
||||||
|
#if !defined(__APPLE__)
|
||||||
|
throw std::runtime_error("local longhaul is only supported on macOS");
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
ggml_backend_dev_t first = pimpl->dev_layer.front().dev;
|
||||||
|
for (int il = 1; il < n_layer_all; ++il) {
|
||||||
|
if (pimpl->dev_layer[il].dev != first) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RPC longhaul v1 requires every model layer on one RPC device");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pimpl->longhaul_remote = true;
|
||||||
}
|
}
|
||||||
ml.configure_longhaul(params.longhaul_cache_bytes, n_expert, n_layer_all);
|
ml.configure_longhaul(params.longhaul_cache_bytes, n_expert, n_layer_all);
|
||||||
if (ml.longhaul_slots < (size_t) n_expert_used) {
|
if (ml.longhaul_slots < (size_t) n_expert_used) {
|
||||||
@@ -1694,7 +1715,8 @@ bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
|||||||
|
|
||||||
if (params.load_mode == LLAMA_LOAD_MODE_LONGHAUL) {
|
if (params.load_mode == LLAMA_LOAD_MODE_LONGHAUL) {
|
||||||
pimpl->longhaul = std::make_unique<llama_longhaul_cache>(
|
pimpl->longhaul = std::make_unique<llama_longhaul_cache>(
|
||||||
std::move(ml.files), std::move(ml.longhaul_sources), ml.longhaul_slots, hparams.n_expert, hparams.n_layer_all);
|
std::move(ml.files), std::move(ml.longhaul_sources), std::move(ml.longhaul_shards),
|
||||||
|
ml.longhaul_slots, hparams.n_expert, hparams.n_layer_all, pimpl->longhaul_remote);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+13
-8
@@ -263,16 +263,21 @@ static bool llama_prepare_model_devices(const llama_model_params & params, llama
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add RPC servers at the front of the list to minimize network transfers
|
if (params.load_mode == LLAMA_LOAD_MODE_LONGHAUL && !rpc_servers.empty()) {
|
||||||
model->devices.insert(model->devices.begin(), rpc_servers.begin(), rpc_servers.end());
|
// RPC longhaul v1 keeps the entire paging domain on one remote device.
|
||||||
|
model->devices.push_back(rpc_servers.front());
|
||||||
|
} else {
|
||||||
|
// add RPC servers at the front of the list to minimize network transfers
|
||||||
|
model->devices.insert(model->devices.begin(), rpc_servers.begin(), rpc_servers.end());
|
||||||
|
|
||||||
// add GPUs
|
// add GPUs
|
||||||
model->devices.insert(model->devices.end(), gpus.begin(), gpus.end());
|
model->devices.insert(model->devices.end(), gpus.begin(), gpus.end());
|
||||||
|
|
||||||
// add integrated GPUs only if no discrete GPUs were found
|
// add integrated GPUs only if no discrete GPUs were found
|
||||||
// (RPC servers do not count, otherwise the local iGPU would be dropped on iGPU+RPC setups)
|
// (RPC servers do not count, otherwise the local iGPU would be dropped on iGPU+RPC setups)
|
||||||
if (gpus.empty()) {
|
if (gpus.empty()) {
|
||||||
model->devices.insert(model->devices.end(), igpus.begin(), igpus.end());
|
model->devices.insert(model->devices.end(), igpus.begin(), igpus.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ struct longhaul_fixture {
|
|||||||
sources.push_back({ weights_b, 0, n_experts * expert_size, expert_size, 0 });
|
sources.push_back({ weights_b, 0, n_experts * expert_size, expert_size, 0 });
|
||||||
}
|
}
|
||||||
cache = std::make_unique<llama_longhaul_cache>(
|
cache = std::make_unique<llama_longhaul_cache>(
|
||||||
std::move(files), std::move(sources), n_slots, n_experts, 1);
|
std::move(files), std::move(sources), std::vector<llama_model_loader::longhaul_shard>{},
|
||||||
|
n_slots, n_experts, 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
~longhaul_fixture() {
|
~longhaul_fixture() {
|
||||||
|
|||||||
+11
-1
@@ -36,6 +36,17 @@ flowchart TD
|
|||||||
By default, `ggml-rpc-server` exposes all available accelerator devices on the host.
|
By default, `ggml-rpc-server` exposes all available accelerator devices on the host.
|
||||||
If there are no accelerators, it exposes a single `CPU` device.
|
If there are no accelerators, it exposes a single `CPU` device.
|
||||||
|
|
||||||
|
For server-resident longhaul MoE paging, place the same GGUF shards on the RPC
|
||||||
|
host and point the server at their directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ bin/ggml-rpc-server --device MTL0 --longhaul-root /path/to/model-directory
|
||||||
|
```
|
||||||
|
|
||||||
|
The client can then use `--rpc`, `--longhaul`, and `--longhaul-cache` together.
|
||||||
|
RPC longhaul v1 requires one remote Metal device. Expert cache misses are read
|
||||||
|
from the RPC host's local files rather than uploaded by the client.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Remote hosts
|
### Remote hosts
|
||||||
@@ -107,4 +118,3 @@ Use the `GGML_RPC_DEBUG` environment variable to enable debug messages from `ggm
|
|||||||
```bash
|
```bash
|
||||||
$ GGML_RPC_DEBUG=1 bin/ggml-rpc-server
|
$ GGML_RPC_DEBUG=1 bin/ggml-rpc-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -173,6 +174,7 @@ struct rpc_server_params {
|
|||||||
std::string host = "127.0.0.1";
|
std::string host = "127.0.0.1";
|
||||||
int port = 50052;
|
int port = 50052;
|
||||||
bool use_cache = false;
|
bool use_cache = false;
|
||||||
|
std::string longhaul_root;
|
||||||
int n_threads = std::max(1U, std::thread::hardware_concurrency()/2);
|
int n_threads = std::max(1U, std::thread::hardware_concurrency()/2);
|
||||||
std::vector<std::string> devices;
|
std::vector<std::string> devices;
|
||||||
};
|
};
|
||||||
@@ -186,6 +188,7 @@ static void print_usage(int /*argc*/, char ** argv, rpc_server_params params) {
|
|||||||
fprintf(stderr, " -H, --host HOST host to bind to (default: %s)\n", params.host.c_str());
|
fprintf(stderr, " -H, --host HOST host to bind to (default: %s)\n", params.host.c_str());
|
||||||
fprintf(stderr, " -p, --port PORT port to bind to (default: %d)\n", params.port);
|
fprintf(stderr, " -p, --port PORT port to bind to (default: %d)\n", params.port);
|
||||||
fprintf(stderr, " -c, --cache enable local file cache\n");
|
fprintf(stderr, " -c, --cache enable local file cache\n");
|
||||||
|
fprintf(stderr, " --longhaul-root PATH serve longhaul expert data from this model directory\n");
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +236,11 @@ static bool rpc_server_params_parse(int argc, char ** argv, rpc_server_params &
|
|||||||
}
|
}
|
||||||
} else if (arg == "-c" || arg == "--cache") {
|
} else if (arg == "-c" || arg == "--cache") {
|
||||||
params.use_cache = true;
|
params.use_cache = true;
|
||||||
|
} else if (arg == "--longhaul-root") {
|
||||||
|
if (++i >= argc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
params.longhaul_root = argv[i];
|
||||||
} else if (arg == "-h" || arg == "--help") {
|
} else if (arg == "-h" || arg == "--help") {
|
||||||
print_usage(argc, argv, params);
|
print_usage(argc, argv, params);
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -313,6 +321,23 @@ int main(int argc, char * argv[]) {
|
|||||||
fprintf(stderr, "No devices found\n");
|
fprintf(stderr, "No devices found\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!params.longhaul_root.empty()) {
|
||||||
|
std::error_code ec;
|
||||||
|
const auto root = std::filesystem::canonical(params.longhaul_root, ec);
|
||||||
|
if (ec || !std::filesystem::is_directory(root, ec)) {
|
||||||
|
fprintf(stderr, "Invalid longhaul root: %s\n", params.longhaul_root.c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
params.longhaul_root = root.string();
|
||||||
|
for (auto * dev : devices) {
|
||||||
|
auto * dev_reg = ggml_backend_dev_backend_reg(dev);
|
||||||
|
if (!dev_reg || strcmp(ggml_backend_reg_name(dev_reg), "MTL") != 0) {
|
||||||
|
fprintf(stderr, "--longhaul-root currently requires Metal devices\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
std::string endpoint = params.host + ":" + std::to_string(params.port);
|
std::string endpoint = params.host + ":" + std::to_string(params.port);
|
||||||
const char * cache_dir = nullptr;
|
const char * cache_dir = nullptr;
|
||||||
std::string cache_dir_str;
|
std::string cache_dir_str;
|
||||||
@@ -331,12 +356,19 @@ int main(int argc, char * argv[]) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto start_server_fn = (decltype(ggml_backend_rpc_start_server)*) ggml_backend_reg_get_proc_address(reg, "ggml_backend_rpc_start_server");
|
auto start_server_fn = (decltype(ggml_backend_rpc_start_server_with_options)*)
|
||||||
|
ggml_backend_reg_get_proc_address(reg, "ggml_backend_rpc_start_server_with_options");
|
||||||
if (!start_server_fn) {
|
if (!start_server_fn) {
|
||||||
fprintf(stderr, "Failed to obtain RPC backend start server function\n");
|
fprintf(stderr, "Failed to obtain RPC backend start server function with options\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
start_server_fn(endpoint.c_str(), cache_dir, params.n_threads, devices.size(), devices.data());
|
start_server_fn(
|
||||||
|
endpoint.c_str(),
|
||||||
|
cache_dir,
|
||||||
|
params.longhaul_root.empty() ? nullptr : params.longhaul_root.c_str(),
|
||||||
|
params.n_threads,
|
||||||
|
devices.size(),
|
||||||
|
devices.data());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user