Compare commits

...
7 Commits
8 changed files with 402 additions and 63 deletions
+12
View File
@@ -208,6 +208,18 @@ extern "C" {
// Context management and operations for faster communication between backends, used for tensor parallelism (meta backend)
typedef void * (*ggml_backend_comm_init_t)(ggml_backend_t * backends, size_t n_backends);
// Builds the communicator for the case where each rank is a separate process, so a process
// owns one backend of the communicator rather than all of them.
//
// The caller provides bootstrap_callback, which moves bytes between the ranks before the
// communicator exists. Each rank passes size bytes in send_buf, and recv_buf receives
// world_size * size bytes in rank order. The backend decides what to exchange and how many
// times to call it, and every rank must make the same calls in the same order.
// comm_init_rank blocks until all world_size ranks have joined.
typedef bool (*ggml_backend_comm_bootstrap_callback)(void * user_data, const void * send_buf, void * recv_buf, size_t size);
typedef void * (*ggml_backend_comm_init_rank_t)(ggml_backend_t backend, int rank, int world_size, ggml_backend_comm_bootstrap_callback bootstrap_callback, void * bootstrap_callback_data);
typedef void (*ggml_backend_comm_free_t)(void * comm_ctx);
typedef bool (*ggml_backend_comm_allreduce_tensor_t)(void * comm_ctx, struct ggml_tensor ** tensors);
+5 -2
View File
@@ -1001,10 +1001,13 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state(
} break;
case GGML_OP_PAD_REFLECT_1D:
case GGML_OP_ROLL:
case GGML_OP_ARANGE:
case GGML_OP_TIMESTEP_EMBEDDING: {
split_state = handle_generic(src_ss, /*scalar_only =*/ true);
} break;
case GGML_OP_ARANGE: {
// no sources, every device computes the full range
split_state = {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1};
} break;
case GGML_OP_ARGSORT:
case GGML_OP_TOP_K: {
split_state = handle_per_row(src_ss);
@@ -1743,7 +1746,7 @@ static ggml_backend_buffer_t ggml_backend_meta_buffer_type_alloc_buffer(ggml_bac
static ggml_backend_buffer_t ggml_backend_meta_buffer_type_alloc_buffer_n(ggml_backend_buffer_type_t buft, ggml_tensor ** tensors, int n_tensors) {
const size_t n_simple_bufts = ggml_backend_meta_buft_n_bufts(buft);
constexpr size_t compute_headroom = 16; // Maximum number of views per statically allocated tensor that can be created between evals.
constexpr size_t compute_headroom = 32; // Maximum number of views per statically allocated tensor that can be created between evals.
const ggml_init_params params_static = {
/*.mem_size =*/ n_tensors * ggml_tensor_overhead(),
/*.mem_buffer =*/ nullptr,
+95 -1
View File
@@ -1026,6 +1026,18 @@ struct ggml_backend_cuda_comm_context {
ggml_cuda_ar_pipeline * ar_pipeline = nullptr;
// Number of ranks in the communicator when each rank is a separate process. Zero unless
// comm_init_rank created this context.
int world_size = 0;
// Number of ranks taking part in the reduction. For a communicator from comm_init_rank this is
// world_size. Otherwise it is the number of backends this process holds. The NCCL reduction
// uses it to choose the tensor size below which it reduces in F32 rather than converting to
// BF16 first.
size_t n_ranks() const {
return world_size > 0 ? (size_t) world_size : backends.size();
}
#ifdef GGML_USE_NCCL
std::vector<ncclComm_t> comms;
#endif // GGML_USE_NCCL
@@ -1053,6 +1065,7 @@ static bool ggml_backend_cuda_comm_allreduce_nccl(
}
const size_t n_backends = comm_ctx->backends.size();
const size_t n_ranks = comm_ctx->n_ranks();
for (size_t i = 0; i < n_backends; ++i) {
GGML_ASSERT(tensors[i] != nullptr);
@@ -1062,7 +1075,7 @@ static bool ggml_backend_cuda_comm_allreduce_nccl(
// For small tensors, simply reduce them as FP32.
// The following heuristic for how "small" a tensor should be is based on RTX 4090s connected via 16x PCIe 4.0.
if ((n_backends <= 2 && ne < 32768) || (n_backends == 3 && ne < 131072) || (n_backends >= 4 && ne < 262144)) {
if ((n_ranks <= 2 && ne < 32768) || (n_ranks == 3 && ne < 131072) || (n_ranks >= 4 && ne < 262144)) {
for (size_t i = 0; i < n_backends; ++i) {
if ((tensors[i]->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) {
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *) comm_ctx->backends[i]->context;
@@ -1293,6 +1306,82 @@ static void * ggml_backend_cuda_comm_init(ggml_backend_t * backends, size_t n_ba
return ret;
}
#ifdef GGML_USE_NCCL
// Builds the communicator with ncclCommInitRank, for the case where each rank is its own
// process. Blocks until all world_size ranks have joined.
static void * ggml_backend_cuda_comm_init_rank(ggml_backend_t backend, int rank, int world_size,
ggml_backend_comm_bootstrap_callback bootstrap_callback, void * bootstrap_callback_data) {
if (world_size < 1 || rank < 0 || rank >= world_size) {
GGML_LOG_ERROR("%s: invalid rank %d for world size %d\n", __func__, rank, world_size);
return nullptr;
}
// Every rank sends whether its local checks passed, and rank 0 also sends the NCCL unique id.
// So all ranks either join together or fail together, and no rank waits in ncclCommInitRank alone.
struct bootstrap_msg {
uint8_t ok;
ncclUniqueId id;
};
bootstrap_msg local;
memset(&local, 0, sizeof(local));
local.ok = 1;
if (!ggml_backend_is_cuda(backend)) {
GGML_LOG_ERROR("%s: backend is not a CUDA backend\n", __func__);
local.ok = 0;
} else {
// NCCL requires one distinct physical GPU per rank, see ggml_backend_cuda_comm_init_nccl.
const ggml_cuda_device_info & info = ggml_cuda_info();
if (info.device_count > info.physical_device_count) {
GGML_LOG_ERROR("%s: CUDA virtual devices are in use, which NCCL does not support\n", __func__);
local.ok = 0;
}
}
if (local.ok && rank == 0) {
const ncclResult_t rc = ncclGetUniqueId(&local.id);
if (rc != ncclSuccess) {
GGML_LOG_ERROR("%s: ncclGetUniqueId failed (%s)\n", __func__, ncclGetErrorString(rc));
local.ok = 0;
}
}
std::vector<bootstrap_msg> all(world_size);
if (!bootstrap_callback(bootstrap_callback_data, &local, all.data(), sizeof(local))) {
GGML_LOG_ERROR("%s: bootstrap exchange failed on rank %d\n", __func__, rank);
return nullptr;
}
for (int i = 0; i < world_size; i++) {
if (!all[i].ok) {
GGML_LOG_ERROR("%s: rank %d failed its setup checks\n", __func__, i);
return nullptr;
}
}
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *) backend->context;
auto * ret = new ggml_backend_cuda_comm_context;
ret->backends.push_back(backend);
ret->dev_ids.push_back(cuda_ctx->device);
ret->world_size = world_size;
ret->comms.resize(1);
ret->try_allreduce = ggml_backend_cuda_comm_try_allreduce_nccl;
ggml_cuda_set_device(cuda_ctx->device);
const ncclResult_t rc = ncclCommInitRank(&ret->comms[0], world_size, all[0].id, rank);
if (rc != ncclSuccess) {
GGML_LOG_ERROR("%s: ncclCommInitRank failed for rank %d/%d (%s)\n",
__func__, rank, world_size, ncclGetErrorString(rc));
ret->comms.clear();
delete ret;
return nullptr;
}
GGML_LOG_INFO("%s: joined NCCL communicator as rank %d/%d on device %d\n",
__func__, rank, world_size, cuda_ctx->device);
return ret;
}
#endif // GGML_USE_NCCL
// Top-level dispatch -- calls the function pointer chosen by comm_init.
// Returns false to let the meta-backend's butterfly run.
static bool ggml_backend_cuda_comm_allreduce_tensor(void * comm_ctx_v, struct ggml_tensor ** tensors) {
@@ -6287,6 +6376,11 @@ static void * ggml_backend_cuda_reg_get_proc_address(ggml_backend_reg_t reg, con
if (strcmp(name, "ggml_backend_comm_allreduce_tensor") == 0) {
return (void *)ggml_backend_cuda_comm_allreduce_tensor;
}
#ifdef GGML_USE_NCCL
if (strcmp(name, "ggml_backend_comm_init_rank") == 0) {
return (void *)ggml_backend_cuda_comm_init_rank;
}
#endif // GGML_USE_NCCL
if (strcmp(name, "ggml_backend_register_host_buffer") == 0) {
return (void *)ggml_backend_cuda_register_host_buffer;
}
+218 -56
View File
@@ -1424,6 +1424,9 @@ public:
private:
void sync_all_backends();
void comm_free_device(uint32_t dev_id);
static bool comm_allreduce_pairwise(void * comm_ctx_v, ggml_tensor ** tensors);
static bool comm_bootstrap_exchange(void * user_data, const void * send_buf, void * recv_buf, size_t size);
bool get_cached_file(uint64_t hash, std::vector<uint8_t> & data);
ggml_tensor * deserialize_tensor(struct ggml_context * ctx, const rpc_tensor * tensor);
ggml_tensor * create_node(uint64_t id,
@@ -1432,15 +1435,26 @@ private:
std::unordered_map<uint64_t, struct ggml_tensor*> & tensor_map);
// pairwise allreduce over a direct connection to the peer server
// Per-device state for one communicator.
struct comm_state {
socket_ptr peer;
// Sockets to the other servers. Rank 0 holds one per peer in rank order, so peers[i] is
// rank i + 1. Every other rank holds one to rank 0. The pairwise path reduces over peers[0].
std::vector<socket_ptr> peers;
uint32_t rank = 0;
uint32_t world = 0;
ggml_backend_t pairwise_backend = nullptr;
ggml_backend_buffer_ptr scratch;
size_t scratch_size = 0;
std::vector<uint8_t> send_buf;
std::vector<uint8_t> recv_buf;
// The backend's communicator. Null on the pairwise path, which has none.
void * backend_comm_ctx = nullptr;
// The chosen reduction, set once a path is agreed. Receives backend_comm_ctx, or
// comm_state on the pairwise path. A communicator covers one device, so the caller
// passes a one-element tensor array.
ggml_backend_comm_allreduce_tensor_t allreduce_fn = nullptr;
};
std::vector<ggml_backend_t> backends;
@@ -2219,78 +2233,188 @@ void rpc_server::sync_all_backends() {
}
}
// The comm link between two servers uses the same caps negotiation as the client HELLO,
void rpc_server::comm_free_device(uint32_t dev_id) {
comm_state & state = comm_states[dev_id];
if (state.backend_comm_ctx != nullptr) {
auto comm_free = (ggml_backend_comm_free_t) ggml_backend_reg_get_proc_address(
ggml_backend_dev_backend_reg(ggml_backend_get_device(backends[dev_id])), "ggml_backend_comm_free");
if (comm_free != nullptr) {
comm_free(state.backend_comm_ctx);
}
}
state = comm_state();
}
// Exchange RPC transport capabilities over a peer connection. recv_first selects which side
// receives first. Both sides receiving first would deadlock.
static bool comm_caps_handshake(const socket_ptr & peer, bool recv_first) {
uint8_t local_caps[RPC_CONN_CAPS_SIZE] = {};
uint8_t remote_caps[RPC_CONN_CAPS_SIZE] = {};
peer->get_caps(local_caps);
if (recv_first) {
if (!peer->recv_data(remote_caps, sizeof(remote_caps)) ||
!peer->send_data(local_caps, sizeof(local_caps))) {
return false;
}
} else {
if (!peer->send_data(local_caps, sizeof(local_caps)) ||
!peer->recv_data(remote_caps, sizeof(remote_caps))) {
return false;
}
}
peer->update_caps(remote_caps);
return true;
}
// The comm link between servers uses the same caps negotiation as the client HELLO,
// so it gets the same transport upgrades (e.g. RDMA).
bool rpc_server::comm_init(const rpc_msg_comm_init_req & request, rpc_msg_comm_init_rsp & response) {
response.ok = 0;
if (request.device >= backends.size() || request.world != 2 || request.rank >= request.world) {
if (request.device >= backends.size() || request.world < 2 || request.rank >= request.world) {
return true;
}
comm_state & state = comm_states[request.device];
if (state.peer != nullptr) {
const uint32_t dev_id = request.device;
comm_state & state = comm_states[dev_id];
if (state.allreduce_fn != nullptr) {
response.ok = 1;
return true;
}
uint8_t local_caps[RPC_CONN_CAPS_SIZE] = {};
uint8_t remote_caps[RPC_CONN_CAPS_SIZE] = {};
// Joining a communicator blocks until every rank arrives, so drain outstanding work first.
sync_all_backends();
// Rank 0 listens and accepts world-1 peers; every other rank connects back to it.
if (request.rank == 0) {
socket_ptr srv = socket_t::create_server("0.0.0.0", request.port);
if (srv == nullptr) {
LOG_ERROR("[%s] failed to listen on comm port %u\n", __func__, request.port);
return true;
}
state.peer = srv->accept();
if (state.peer == nullptr) {
return true;
// Store peers[i] as rank i + 1, as comm_bootstrap_exchange needs.
// Each peer sends its rank because peers may connect in any order.
state.peers.resize(request.world - 1);
for (uint32_t i = 1; i < request.world; i++) {
socket_ptr peer = srv->accept();
uint32_t peer_rank = 0;
if (peer == nullptr || !comm_caps_handshake(peer, /*recv_first =*/ true) ||
!peer->recv_data(&peer_rank, sizeof(peer_rank)) ||
peer_rank == 0 || peer_rank >= request.world || state.peers[peer_rank - 1] != nullptr) {
LOG_ERROR("[%s] bad peer connection (rank %u)\n", __func__, peer_rank);
state.peers.clear();
return true;
}
state.peers[peer_rank - 1] = std::move(peer);
}
if (!state.peer->recv_data(remote_caps, sizeof(remote_caps))) {
state.peer = nullptr;
return true;
}
state.peer->get_caps(local_caps);
if (!state.peer->send_data(local_caps, sizeof(local_caps))) {
state.peer = nullptr;
return true;
}
state.peer->update_caps(remote_caps);
} else {
const std::string host(request.host, strnlen(request.host, sizeof(request.host)));
socket_ptr peer;
// rank 0 may not be listening yet, retry for a few seconds
for (int i = 0; i < 100 && state.peer == nullptr; i++) {
state.peer = socket_t::connect(host.c_str(), request.port);
if (state.peer == nullptr) {
for (int i = 0; i < 100 && peer == nullptr; i++) {
peer = socket_t::connect(host.c_str(), request.port);
if (peer == nullptr) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
if (state.peer == nullptr) {
if (peer == nullptr) {
LOG_ERROR("[%s] failed to connect to peer %s:%u\n", __func__, host.c_str(), request.port);
return true;
}
state.peer->get_caps(local_caps);
if (!state.peer->send_data(local_caps, sizeof(local_caps)) ||
!state.peer->recv_data(remote_caps, sizeof(remote_caps))) {
state.peer = nullptr;
if (!comm_caps_handshake(peer, /*recv_first =*/ false) ||
!peer->send_data(&request.rank, sizeof(request.rank)) || !peer->flush()) {
return true;
}
state.peer->update_caps(remote_caps);
state.peers.push_back(std::move(peer));
}
state.rank = request.rank;
state.world = request.world;
LOG_INFO("[%s] device %u joined pairwise comm as rank %u\n", __func__, request.device, request.rank);
// Forces the pairwise path, for comparing the two on one build. Setting it on any one rank
// is enough, because the negotiation spreads the decision.
const bool no_backend_communicator = std::getenv("GGML_RPC_NO_BACKEND_COMMUNICATOR") != nullptr;
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backends[dev_id]));
auto init_rank = (ggml_backend_comm_init_rank_t)
ggml_backend_reg_get_proc_address(reg, "ggml_backend_comm_init_rank");
auto allreduce_fn = (ggml_backend_comm_allreduce_tensor_t)
ggml_backend_reg_get_proc_address(reg, "ggml_backend_comm_allreduce_tensor");
// The ranks must agree on backend communicator or pairwise. Every rank shares whether its
// backend provides a communicator, and all ranks use it only if every rank can.
const uint8_t self_can_use_backend_comm = !no_backend_communicator && init_rank != nullptr && allreduce_fn != nullptr;
std::vector<uint8_t> all_can_use_backend_comm(request.world);
if (!comm_bootstrap_exchange(&state, &self_can_use_backend_comm, all_can_use_backend_comm.data(),
sizeof(self_can_use_backend_comm))) {
state.peers.clear();
return true;
}
const bool use_backend_communicator = std::all_of(all_can_use_backend_comm.begin(), all_can_use_backend_comm.end(),
[](uint8_t c) { return c != 0; });
if (use_backend_communicator) {
void * backend_ctx = init_rank(backends[dev_id], (int) request.rank, (int) request.world, comm_bootstrap_exchange, &state);
if (backend_ctx == nullptr) {
LOG_ERROR("[%s] failed to join communicator as rank %u/%u\n", __func__, request.rank, request.world);
state.peers.clear();
return true;
}
state.backend_comm_ctx = backend_ctx;
state.allreduce_fn = allreduce_fn;
// The sockets have done their job; the collective runs between the backends from here on.
state.peers.clear();
LOG_INFO("[%s] device %u using backend communicator, rank %u/%u\n", __func__, dev_id, request.rank, request.world);
response.ok = 1;
return true;
}
// No backend communicator, so fall back to the pairwise exchange, which only covers two ranks.
if (request.world != 2) {
LOG_ERROR("[%s] world size %u requires a backend communicator\n", __func__, request.world);
state.peers.clear();
return true;
}
state.pairwise_backend = backends[dev_id];
state.allreduce_fn = comm_allreduce_pairwise;
LOG_INFO("[%s] device %u using pairwise communicator, rank %u\n", __func__, dev_id, request.rank);
response.ok = 1;
return true;
}
bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
if (request.device >= backends.size()) {
return false;
// Bootstrap callback for ggml_backend_comm_init_rank_t, over the peer sockets. Rank 0 collects
// size bytes from every rank in rank order, then sends the whole array back to every rank.
// user_data is the comm_state.
bool rpc_server::comm_bootstrap_exchange(void * user_data, const void * send_buf, void * recv_buf, size_t size) {
comm_state & state = *(comm_state *) user_data;
uint8_t * recv = (uint8_t *) recv_buf;
const size_t total = (size_t) state.world * size;
if (state.rank == 0) {
memcpy(recv, send_buf, size);
for (size_t i = 0; i < state.peers.size(); i++) {
if (!state.peers[i]->recv_data(recv + (i + 1) * size, size)) {
return false;
}
}
for (const socket_ptr & peer : state.peers) {
if (!peer->send_data(recv, total) || !peer->flush()) {
return false;
}
}
return true;
}
comm_state & state = comm_states[request.device];
if (state.peer == nullptr) {
LOG_ERROR("[%s] no communicator for device %u\n", __func__, request.device);
return false;
}
ggml_backend_t backend = backends[request.device];
return state.peers[0]->send_data(send_buf, size) && state.peers[0]->flush() &&
state.peers[0]->recv_data(recv, total);
}
// Reduces this rank's contribution in place, exchanging partials with the peer over peers[0].
// Used when the backend provides no communicator. comm_ctx_v is the comm_state.
bool rpc_server::comm_allreduce_pairwise(void * comm_ctx_v, ggml_tensor ** tensors) {
comm_state & state = *(comm_state *) comm_ctx_v;
ggml_tensor * t_dst = tensors[0];
ggml_backend_t backend = state.pairwise_backend;
const size_t nbytes = ggml_nbytes(t_dst);
const int64_t ne = ggml_nelements(t_dst);
size_t ctx_size = 16*ggml_tensor_overhead() + 2*ggml_graph_overhead_custom(8, false);
struct ggml_init_params params = {
@@ -2301,16 +2425,7 @@ bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
ggml_context_ptr ctx_ptr { ggml_init(params) };
GGML_ASSERT(ctx_ptr != nullptr);
ggml_context * ctx = ctx_ptr.get();
ggml_tensor * t_dst = deserialize_tensor(ctx, &request.tensor);
if (t_dst == nullptr || t_dst->buffer == nullptr) {
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
const size_t nbytes = ggml_nbytes(t_dst);
const int64_t ne = ggml_nelements(t_dst);
if (nbytes == 0) {
return true;
}
// reduce large partials in bf16 to halve the wire bytes; small (decode-sized) ones
// stay f32 since the extra casts and sync cost more than the bytes saved
const bool wire_bf16 = t_dst->type == GGML_TYPE_F32 && ne >= 32768;
@@ -2366,13 +2481,13 @@ bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
// rank 0 sends first, rank 1 receives first, so large payloads cannot deadlock
if (state.rank == 0) {
if (!state.peer->send_data(state.send_buf.data(), wire_bytes) || !state.peer->flush() ||
!state.peer->recv_data(state.recv_buf.data(), wire_bytes)) {
if (!state.peers[0]->send_data(state.send_buf.data(), wire_bytes) || !state.peers[0]->flush() ||
!state.peers[0]->recv_data(state.recv_buf.data(), wire_bytes)) {
return false;
}
} else {
if (!state.peer->recv_data(state.recv_buf.data(), wire_bytes) ||
!state.peer->send_data(state.send_buf.data(), wire_bytes) || !state.peer->flush()) {
if (!state.peers[0]->recv_data(state.recv_buf.data(), wire_bytes) ||
!state.peers[0]->send_data(state.send_buf.data(), wire_bytes) || !state.peers[0]->flush()) {
return false;
}
}
@@ -2402,11 +2517,50 @@ bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
return true;
}
bool rpc_server::comm_allreduce(const rpc_msg_comm_allreduce_req & request) {
if (request.device >= backends.size()) {
return false;
}
comm_state & state = comm_states[request.device];
if (state.allreduce_fn == nullptr) {
LOG_ERROR("[%s] no communicator for device %u\n", __func__, request.device);
return false;
}
struct ggml_init_params params = {
/*.mem_size =*/ ggml_tensor_overhead(),
/*.mem_buffer =*/ NULL,
/*.no_alloc =*/ true,
};
ggml_context_ptr ctx_ptr { ggml_init(params) };
GGML_ASSERT(ctx_ptr != nullptr);
ggml_tensor * t_dst = deserialize_tensor(ctx_ptr.get(), &request.tensor);
if (t_dst == nullptr || t_dst->buffer == nullptr) {
LOG_ERROR("[%s] error deserializing tensor\n", __func__);
return false;
}
if (ggml_nbytes(t_dst) == 0) {
return true;
}
// Neither path needs a wait here. The pairwise path waits internally, and the backend orders
// its reduction after the work that produced the partial sum.
void * comm_ctx = state.backend_comm_ctx != nullptr ? state.backend_comm_ctx : &state;
if (!state.allreduce_fn(comm_ctx, &t_dst)) {
LOG_ERROR("[%s] allreduce failed for tensor %s\n", __func__, t_dst->name);
return false;
}
return true;
}
bool rpc_server::comm_free(const rpc_msg_comm_free_req & request) {
if (request.device >= backends.size()) {
return false;
}
comm_states[request.device] = comm_state();
// Wait for outstanding work. Destroying a communicator while its kernels are still running
// is unsafe.
sync_all_backends();
comm_free_device(request.device);
return true;
}
@@ -2426,6 +2580,14 @@ bool rpc_server::get_device_memory(const rpc_msg_get_device_memory_req & request
}
rpc_server::~rpc_server() {
// Wait for outstanding work. Destroying a communicator while its kernels are still running
// is unsafe.
sync_all_backends();
// Free any communicator still open. A client can disconnect without sending COMM_FREE.
for (uint32_t dev_id = 0; dev_id < comm_states.size(); dev_id++) {
comm_free_device(dev_id);
}
for (auto buffer : buffers) {
ggml_backend_buffer_free(buffer);
}
@@ -2991,7 +3153,7 @@ static void ggml_backend_rpc_comm_free(void * comm_ctx_v) {
}
static void * ggml_backend_rpc_comm_init(ggml_backend_t * backends, size_t n_backends) {
if (n_backends != 2 || std::getenv("GGML_RPC_NO_COMM") != nullptr) {
if (n_backends < 2 || std::getenv("GGML_RPC_NO_COMM") != nullptr) {
return nullptr;
}
std::vector<ggml_backend_rpc_comm_context::rank_info> ranks;
-1
View File
@@ -1351,7 +1351,6 @@ bool llm_arch_supports_sm_tensor(const llm_arch & arch) {
case LLM_ARCH_KIMI_LINEAR:
case LLM_ARCH_BAILINGMOE3:
case LLM_ARCH_KIMI_K3:
case LLM_ARCH_GLM5_NEXT:
case LLM_ARCH_QWEN3TTS:
return false;
default:
+61 -1
View File
@@ -402,6 +402,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
const std::string tensor_name = tensor->name;
const bool is_dsv4 = ud->model->arch == LLM_ARCH_DEEPSEEK4 || ud->model->arch == LLM_ARCH_DEEPSEEK41 ||
(ud->model->arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0);
const bool is_glm5_next = ud->model->arch == LLM_ARCH_GLM5_NEXT;
static const std::regex pattern_q_weight ("blk\\.\\d*\\.attn_q.weight");
static const std::regex pattern_kv_weight ("blk\\.\\d*\\.attn_(k|v).weight");
@@ -421,6 +422,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
static const std::regex pattern_attn_out_a_weight("blk\\.\\d*\\.attn_output_a\\.weight");
static const std::regex pattern_attn_out_b_weight("blk\\.\\d*\\.attn_output_b\\.weight");
static const std::regex pattern_attn_q_b_weight ("blk\\.\\d*\\.attn_q_b\\.weight");
static const std::regex pattern_attn_kv_b_weight("blk\\.\\d*\\.attn_(k|v)_b\\.weight");
static const std::regex pattern_attn_gate_weight("blk\\.\\d*\\.attn_gate.weight");
static const std::regex pattern_ssm_dt ("blk\\.\\d*\\.ssm_dt.bias");
@@ -433,6 +435,8 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
static const std::regex pattern_s_cache ("cache_s_l\\d*");
static const std::regex pattern_ssm_conv1d ("blk\\.\\d*\\.ssm_conv1d.weight");
static const std::regex pattern_ssm_out_weight ("blk\\.\\d*\\.ssm_out.weight");
static const std::regex pattern_kda_conv1d ("blk\\.\\d*\\.ssm_conv1d_(q|k|v)\\.weight");
static const std::regex pattern_kda_gate_b ("blk\\.\\d*\\.ssm_(f|g)_b\\.weight");
static const std::regex pattern_ffn_up_weight ("blk\\.\\d*\\.ffn_up(_exps)?.weight");
static const std::regex pattern_ffn_up_bias ("blk\\.\\d*\\.ffn_up(_exps)?.bias");
@@ -522,6 +526,25 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
if (std::regex_match(tensor_name, pattern_attn_out_b_weight)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0);
}
}
if (is_glm5_next) {
// split the KDA and MLA heads, the MLA latent cache and the indexer stay mirrored
if (std::regex_match(tensor_name, pattern_kv_cache)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
}
if (std::regex_match(tensor_name, pattern_attn_q_b_weight) || std::regex_match(tensor_name, pattern_kda_gate_b) ||
std::regex_match(tensor_name, pattern_ssm_beta)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight");
}
if (std::regex_match(tensor_name, pattern_attn_kv_b_weight) || std::regex_match(tensor_name, pattern_kda_conv1d)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_2, "attn_output.weight");
}
if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) ||
std::regex_match(tensor_name, pattern_r_cache) || std::regex_match(tensor_name, pattern_s_cache)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output.weight");
}
}
if (is_dsv4 || is_glm5_next) {
if (std::regex_match(tensor_name, pattern_ffn_up_shexp_weight) ||
std::regex_match(tensor_name, pattern_ffn_gate_shexp_weight)) {
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ffn_down_shexp.weight");
@@ -635,6 +658,12 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
// if a model has fused tensors they need to be separated into "segments", see the comments on ggml_backend_meta_split_state struct
auto get_split_segments = [&](int axis, uint32_t il) -> std::vector<std::pair<int64_t, uint32_t>> {
if (is_glm5_next && std::regex_match(tensor_name, pattern_r_cache)) {
// the conv state holds the q, k and v windows one after the other
const int64_t conv_state_size = (hparams.ssm_d_conv - 1) * hparams.n_head(il) * hparams.n_embd_head_kda;
GGML_ASSERT(tensor->ne[axis] == 3*conv_state_size);
return {{conv_state_size, 3}};
}
// TODO: deduplicate condition [TAG_SPLIT_QGATE_QWEN]
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE || ud->model->arch == LLM_ARCH_CLEF ||
ud->model->arch == LLM_ARCH_QWEN4EXP) {
@@ -726,7 +755,38 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str
auto get_split_granularity = [&](int64_t blck_size, uint32_t il, const std::vector<std::pair<int64_t, uint32_t>> & segments) -> std::vector<int64_t> {
// for better performance it may make sense to round up blck_size to a higher power of 2 so that more efficient kernels can be used
if (hparams.is_recr(il)) {
if (is_glm5_next && hparams.is_recr(il)) {
// KDA, each device holds whole heads
const int64_t head_dim = hparams.n_embd_head_kda;
const int64_t blck_size_perf = std::lcm(blck_size, 128);
const int64_t granularity_qkv = std::lcm(blck_size_perf, head_dim);
if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight) ||
std::regex_match(tensor_name, pattern_kda_conv1d) || std::regex_match(tensor_name, pattern_kda_gate_b) ||
std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_attn_out_weight)) {
return {granularity_qkv};
}
if (std::regex_match(tensor_name, pattern_ssm_a) || std::regex_match(tensor_name, pattern_ssm_beta)) {
return {granularity_qkv / head_dim};
}
if (std::regex_match(tensor_name, pattern_r_cache)) {
return std::vector<int64_t>(segments.size(), granularity_qkv * (hparams.ssm_d_conv - 1));
}
if (std::regex_match(tensor_name, pattern_s_cache)) {
return {granularity_qkv * head_dim};
}
} else if (is_glm5_next) {
// MLA, each device holds whole heads
const int64_t granularity_head = std::lcm(hparams.n_embd_head_v_mla(), blck_size) / hparams.n_embd_head_v_mla();
if (std::regex_match(tensor_name, pattern_attn_q_b_weight)) {
return {granularity_head * hparams.n_embd_head_k_mla()};
}
if (std::regex_match(tensor_name, pattern_attn_kv_b_weight)) {
return {granularity_head};
}
if (std::regex_match(tensor_name, pattern_attn_out_weight)) {
return {granularity_head * hparams.n_embd_head_v_mla()};
}
} else if (hparams.is_recr(il)) {
// linear attention
const int64_t head_dim = hparams.ssm_d_state;
const int64_t blck_size_perf = std::lcm(blck_size, 128);
+2 -1
View File
@@ -1072,7 +1072,8 @@ llama_model_glm5_next::graph::build_dsa_layer(
// absorb wk_b so the cache holds only the latent
ggml_tensor * q_absorbed = ggml_permute(ctx0, q, 0, 2, 1, 3);
q_absorbed = ggml_mul_mat(ctx0, layer.wk_b, q_absorbed);
q_absorbed = ggml_permute(ctx0, q_absorbed, 0, 2, 1, 3);
// FIXME: -sm tensor cannot split the per-stream view of a permuted q in build_attn_mha (non-unified KV)
q_absorbed = ggml_cont(ctx0, ggml_permute(ctx0, q_absorbed, 0, 2, 1, 3));
cb(q_absorbed, "q_absorbed", il);
ggml_tensor * kq_mask = inp_attn->get_kq_mask();
+9 -1
View File
@@ -251,12 +251,16 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
|| arch == LLM_ARCH_KIMI_LINEAR
|| arch == LLM_ARCH_BAILINGMOE3
|| arch == LLM_ARCH_KIMI_K3
|| arch == LLM_ARCH_GLM5_NEXT
|| arch == LLM_ARCH_MISTRAL4
|| arch == LLM_ARCH_HY_V4) {
n_embd = 128;
n_head = 1;
n_ff = 192;
} else if (arch == LLM_ARCH_GLM5_NEXT) {
// several heads so that -sm tensor can split them and the MLA flash attention has GQA > 1
n_embd = 128;
n_head = 16;
n_ff = 192;
} else if (arch == LLM_ARCH_NEMOTRON_H || arch == LLM_ARCH_NEMOTRON_H_MOE) {
n_layer = 3;
} else if (arch == LLM_ARCH_CHAMELEON) {
@@ -324,6 +328,10 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {
// GLM5 next KDA heads come from the uniform head count, only head_count_kv is per layer.
if (arch == LLM_ARCH_GLM5_NEXT) {
ms.add_kv(LLM_KV_ATTENTION_HEAD_COUNT, n_head);
// the MLA cache holds one latent head
for (uint32_t & n : n_head_per_layer) {
n = std::min(n, uint32_t(1));
}
} else {
ms.add_kv(LLM_KV_ATTENTION_HEAD_COUNT, n_head_per_layer);
}