From 72792b0ec417cf398949418e57e5d776479fdcf3 Mon Sep 17 00:00:00 2001 From: brain Date: Wed, 23 Sep 2026 04:54:01 +0000 Subject: [PATCH 1/7] tensor-split debug: divisibility preference, DBG prints, whole-dim segments, QWEN35 mirror block, signs per-width --- ggml/src/ggml-backend-meta.cpp | 51 ++++++++++++++++++++++++++++++++-- src/llama-model.cpp | 47 +++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/ggml/src/ggml-backend-meta.cpp b/ggml/src/ggml-backend-meta.cpp index fe58ea3bb7a6..d60ca48fd10f 100644 --- a/ggml/src/ggml-backend-meta.cpp +++ b/ggml/src/ggml-backend-meta.cpp @@ -534,6 +534,14 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( if (scalar_only && ret.axis >= 0 && ret.axis < GGML_MAX_DIMS) { ret = {GGML_BACKEND_SPLIT_AXIS_UNKNOWN, {0}, {1}, 1}; } + if (ret.axis == GGML_BACKEND_SPLIT_AXIS_UNKNOWN) { + fprintf(stderr, "DBG_GENERIC tensor='%s' op=%d", tensor->name, (int)tensor->op); + for (size_t k = 0; k < GGML_MAX_SRC; k++) { + if (tensor->src[k]) fprintf(stderr, " src%zu='%s'[ax=%d ne=%lld,%lld]", k, tensor->src[k]->name, + (int)src_ss[k].axis, (long long)src_ss[k].ne[0], (long long)src_ss[k].ne[1]); + } + fprintf(stderr, "\n"); + } GGML_ASSERT(ret.axis != GGML_BACKEND_SPLIT_AXIS_UNKNOWN); return ret; }; @@ -636,15 +644,28 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( } } // Reshape outputs use one segment; split-state propagation merges source segments. + // Prefer a dim that actually divides across devices (e.g. skip rep=3 tilings); + // first cumulative match kept as fallback = legacy behavior for all working models. int64_t base_ne_out = 1; + int fallback_dim = -1; + uint32_t fallback_nr = 1; for (int dim = 0; dim < GGML_MAX_DIMS; dim++) { base_ne_out *= tensor->ne[dim]; if (base_ne_out % base_ne_in == 0) { - return {ggml_backend_meta_split_axis(dim), {0}, {uint32_t(base_ne_out/base_ne_in)}, 1}; + if (fallback_dim < 0) { + fallback_dim = dim; + fallback_nr = uint32_t(base_ne_out/base_ne_in); + } + if (n_bufs > 0 && tensor->ne[dim] % (int64_t) n_bufs == 0) { + return {ggml_backend_meta_split_axis(dim), {0}, {uint32_t(base_ne_out/base_ne_in)}, 1}; + } } if (base_ne_out > base_ne_in) { GGML_ASSERT(src_ss[0].n_segments == 1); GGML_ASSERT(src_ss[0].nr[0] == 1); + if (fallback_dim >= 0) { + return {ggml_backend_meta_split_axis(fallback_dim), {0}, {fallback_nr}, 1}; + } return {ggml_backend_meta_split_axis(dim), {0}, {1}, 1}; } } @@ -1083,7 +1104,19 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( split_state.ne[j] *= tensor->ne[split_state.axis]; if (split_state.ne[j] != 0 || tensor->src[i]->ne[src_ss[i].axis] != 0) { const int64_t div = tensor->src[i]->ne[src_ss[i].axis] * split_state.nr[0]; - GGML_ASSERT(split_state.ne[j] % div == 0); + if (div != 0 && split_state.ne[j] % div != 0) { + fprintf(stderr, "DBG_CHAIN:"); + { const ggml_tensor * c = tensor; for (int d = 0; d < 7 && c; d++) { + fprintf(stderr, " [%s op=%d]", c->name, (int)c->op); + c = c->src[0]; + } fprintf(stderr, "\n"); } + fprintf(stderr, "DBG_SPLIT_FAIL tensor='%s' op=%d axis=%d ne=[%lld,%lld,%lld,%lld] src='%s' src_axis=%d src_ne=%lld ne_j=%lld div=%lld nr0=%lld nbufs=%zu j=%zu srcop=%d srcsrc0op=%d srcseg=%zu srcne0=%lld srcne1=%lld srcnr0=%u srcnr1=%u\n", + tensor->name, (int)tensor->op, (int)split_state.axis, + (long long)tensor->ne[0], (long long)tensor->ne[1], (long long)tensor->ne[2], (long long)tensor->ne[3], + tensor->src[i]->name, (int)src_ss[i].axis, (long long)tensor->src[i]->ne[src_ss[i].axis], + (long long)split_state.ne[j], (long long)div, (long long)split_state.nr[0], n_bufs, j, (int)tensor->src[i]->op, tensor->src[i]->src[0] ? (int)tensor->src[i]->src[0]->op : -1, src_ss[i].n_segments, (long long)src_ss[i].ne[0], (long long)src_ss[i].ne[1], src_ss[i].nr[0], src_ss[i].nr[1]); + } + GGML_ASSERT(div == 0 || split_state.ne[j] % div == 0); split_state.ne[j] /= div; } } @@ -1095,6 +1128,20 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( for (size_t s = 0; s < src_ss[i].n_segments; s++) { sum += src_ss[i].ne[s*n_bufs + j] * src_ss[i].nr[s]; } + if (!(split_state.ne[j]*split_state.nr[0] * tensor->src[i]->ne[src_ss[i].axis] + == sum * tensor->ne[split_state.axis])) { + fprintf(stderr, "DBG_CONS tensor='%s' op=%d ax=%d ne=[%lld,%lld] srci=%d src='%s' sax=%d sne=[%lld,%lld]\n", + tensor->name, (int)tensor->op, (int)split_state.axis, + (long long)split_state.ne[0], (long long)split_state.ne[1], (int)i, + tensor->src[i]->name, (int)src_ss[i].axis, + (long long)src_ss[i].ne[0], (long long)src_ss[i].ne[1]); + for (size_t k = 0; k < GGML_MAX_SRC; k++) { + if (tensor->src[k]) fprintf(stderr, " src%zu='%s' ax=%d ne=[%lld,%lld] nr0=%u nseg=%zu\n", + k, tensor->src[k]->name, (int)src_ss[k].axis, + (long long)src_ss[k].ne[0], (long long)src_ss[k].ne[1], + src_ss[k].nr[0], src_ss[k].n_segments); + } + } GGML_ASSERT(split_state.ne[j]*split_state.nr[0] * tensor->src[i]->ne[src_ss[i].axis] == sum * tensor->ne[split_state.axis]); } diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 191d025e2810..1902a10d59ac 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -492,6 +492,35 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } } + // Hadamard sign vectors: match their consumers (per width). + // 17408-wide signs serve split FFN activations -> split; 6144-wide serve + // mirrored SSM activations -> mirrored. + { + static const std::regex pattern_signs("^prism\\.hadamard\\.signs\\.[0-9]+$"); + if (std::regex_match(tensor_name, pattern_signs)) { + if (tensor->ne[0] == 17408) { + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); + } + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); + } + } + // QWEN35 linear-attention block: run fully mirrored (replicated) on each device. + // The Hadamard rep=3 tiling cannot shard across 2 devices, so replicate instead. + if (ud->model->arch == LLM_ARCH_QWEN35) { + if (std::regex_match(tensor_name, pattern_attn_gate_weight) || + std::regex_match(tensor_name, pattern_ssm_out_weight) || + std::regex_match(tensor_name, pattern_qkv_weight) || + std::regex_match(tensor_name, pattern_ssm_conv1d) || + std::regex_match(tensor_name, pattern_ssm_dt) || + std::regex_match(tensor_name, pattern_ssm_a) || + std::regex_match(tensor_name, pattern_ssm_alpha) || + std::regex_match(tensor_name, pattern_ssm_beta) || + std::regex_match(tensor_name, pattern_ssm_beta_alpha) || + 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_MIRRORED); + } + } // standard attention if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight)) { return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight"); @@ -606,11 +635,16 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str return {{key_dim, 2 + head_ratio}}; } if (std::regex_match(tensor_name, pattern_attn_gate_weight) || std::regex_match(tensor_name, pattern_ssm_out_weight)) { - return {{key_dim, head_ratio}}; + // whole 6144-dim as ONE segment so uneven ratios (1:2) map whole rep-groups + // to devices instead of cutting every group (needed for Hadamard rep=3 views) + GGML_ASSERT(tensor->ne[axis] == key_dim * head_ratio); + return {{key_dim * head_ratio, 1}}; } if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) || std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta)) { - return {{n_k_heads, head_ratio}}; + // whole dim as ONE segment so uneven ratios stay exact (match gate path 1:2) + GGML_ASSERT(tensor->ne[axis] == n_k_heads * head_ratio); + return {{n_k_heads * head_ratio, 1}}; } if (std::regex_match(tensor_name, pattern_r_cache)) { return {{key_dim * (hparams.ssm_d_conv - 1), 2 + head_ratio}}; @@ -800,6 +834,15 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } split_state.ne[is*ud->n_devices + (j + tc.rotation) % ud->n_devices] = ne_s - low; split_state.nr[is] = nr_s; + fprintf(stderr, "SEG %s ax=%d ne=[%lld,%lld]\n", tensor_name.c_str(), + (int)split_state.axis, + (long long)split_state.ne[is*ud->n_devices+0], (long long)split_state.ne[is*ud->n_devices+1]); + if (tensor_name.find("ssm_out") != std::string::npos || tensor_name.find("attn_gate") != std::string::npos) { + fprintf(stderr, "DBG_SEG tensor=%s axis=%d ne_s=%lld nr_s=%u g=%lld ne=[%lld,%lld] rot=%d split=[%g,%g]\n", + tensor_name.c_str(), (int)split_state.axis, (long long)ne_s, nr_s, (long long)g_s, + (long long)split_state.ne[is*ud->n_devices+0], (long long)split_state.ne[is*ud->n_devices+1], + tc.rotation, tensor_split ? tensor_split[0] : -1.0f, tensor_split && ud->n_devices > 1 ? tensor_split[1] : -1.0f); + } } split_state.n_segments = segments.size(); } else { From 984b9a5cdf2a1e4234ebbf6e962ea1d9167da862 Mon Sep 17 00:00:00 2001 From: brain Date: Wed, 23 Sep 2026 07:44:07 +0000 Subject: [PATCH 2/7] tensor-split: explicit nk-mapping, FFN mirror (PQ2_0), signs split, revert whole-dim+mirror-block --- ggml/src/ggml-backend-meta.cpp | 8 +++++++ src/llama-model.cpp | 40 +++++++++++++--------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/ggml/src/ggml-backend-meta.cpp b/ggml/src/ggml-backend-meta.cpp index d60ca48fd10f..3cface550f69 100644 --- a/ggml/src/ggml-backend-meta.cpp +++ b/ggml/src/ggml-backend-meta.cpp @@ -621,6 +621,14 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( case GGML_BACKEND_SPLIT_AXIS_1: case GGML_BACKEND_SPLIT_AXIS_2: case GGML_BACKEND_SPLIT_AXIS_3: { + // Explicit Hadamard-permute mapping (qwen35 hybrid): a [128,16,3,...] view of an + // axis-0-split 6144-dim splits on nk (axis 1, 8+8). The generic cumulative rule + // would pick the rep dim (size 3, unshardable). FWHT keeps full 128, reps stay whole. + if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0 && + tensor->src[0] && tensor->src[0]->ne[0] == 6144 && + tensor->ne[0] == 128 && tensor->ne[1] == 16 && tensor->ne[2] == 3) { + return {GGML_BACKEND_SPLIT_AXIS_1, {0}, {1}, 1}; + } int64_t base_ne_in = 1; for (int dim = 0; dim <= src_ss[0].axis; dim++) { base_ne_in *= tensor->src[0]->ne[dim]; diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 1902a10d59ac..69fd86290cb8 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -492,35 +492,30 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } } + // Bonsai FFN mirror: PQ2_0-family FFN projections run replicated (their Hadamard + // input prep [1024,34] cannot shard under even ratios; replication is exact math). + // Q4 and other types untouched -> no regression for standard models. + { + static const std::regex pattern_ffn_mir("blk\\.\\d*\\.ffn_(up|gate|down)\\.weight"); + if (std::regex_match(tensor_name, pattern_ffn_mir) && + (tensor->type == GGML_TYPE_PQ2_0 || tensor->type == GGML_TYPE_PTQ1_0)) { + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); + } + } // Hadamard sign vectors: match their consumers (per width). // 17408-wide signs serve split FFN activations -> split; 6144-wide serve // mirrored SSM activations -> mirrored. { static const std::regex pattern_signs("^prism\\.hadamard\\.signs\\.[0-9]+$"); if (std::regex_match(tensor_name, pattern_signs)) { - if (tensor->ne[0] == 17408) { + // signs follow their (split) activation shards elementwise; + // 6144-wide signs serve axis-0-split SSM activations under even ratios + if (tensor->ne[0] == 6144) { return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); } return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } } - // QWEN35 linear-attention block: run fully mirrored (replicated) on each device. - // The Hadamard rep=3 tiling cannot shard across 2 devices, so replicate instead. - if (ud->model->arch == LLM_ARCH_QWEN35) { - if (std::regex_match(tensor_name, pattern_attn_gate_weight) || - std::regex_match(tensor_name, pattern_ssm_out_weight) || - std::regex_match(tensor_name, pattern_qkv_weight) || - std::regex_match(tensor_name, pattern_ssm_conv1d) || - std::regex_match(tensor_name, pattern_ssm_dt) || - std::regex_match(tensor_name, pattern_ssm_a) || - std::regex_match(tensor_name, pattern_ssm_alpha) || - std::regex_match(tensor_name, pattern_ssm_beta) || - std::regex_match(tensor_name, pattern_ssm_beta_alpha) || - 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_MIRRORED); - } - } // standard attention if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight)) { return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight"); @@ -635,16 +630,11 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str return {{key_dim, 2 + head_ratio}}; } if (std::regex_match(tensor_name, pattern_attn_gate_weight) || std::regex_match(tensor_name, pattern_ssm_out_weight)) { - // whole 6144-dim as ONE segment so uneven ratios (1:2) map whole rep-groups - // to devices instead of cutting every group (needed for Hadamard rep=3 views) - GGML_ASSERT(tensor->ne[axis] == key_dim * head_ratio); - return {{key_dim * head_ratio, 1}}; + return {{key_dim, head_ratio}}; } if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) || std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta)) { - // whole dim as ONE segment so uneven ratios stay exact (match gate path 1:2) - GGML_ASSERT(tensor->ne[axis] == n_k_heads * head_ratio); - return {{n_k_heads * head_ratio, 1}}; + return {{n_k_heads, head_ratio}}; } if (std::regex_match(tensor_name, pattern_r_cache)) { return {{key_dim * (hparams.ssm_d_conv - 1), 2 + head_ratio}}; From 64ec876830d8f5476151d6b85def2220917c68e7 Mon Sep 17 00:00:00 2001 From: brain Date: Wed, 23 Sep 2026 10:31:50 +0000 Subject: [PATCH 3/7] tensor-split: output-mirror, bin_bcast rule, seg-dump; PPL: out-CPU 1.03, mirror 3.38 --- ggml/src/ggml-backend-meta.cpp | 19 +++++++++++++++++++ src/llama-model.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/ggml/src/ggml-backend-meta.cpp b/ggml/src/ggml-backend-meta.cpp index 3cface550f69..f68c8ae8db9d 100644 --- a/ggml/src/ggml-backend-meta.cpp +++ b/ggml/src/ggml-backend-meta.cpp @@ -563,6 +563,25 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( return src_ss[0]; // GGML_OP_ADD_ID } GGML_ASSERT(tensor->src[2] == nullptr || src_ss[2].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED); + // Broadcast: one side mirrored (full copy on each device), other side split, + // same shape -> result follows the split side (each device combines local shards). + // Covers Hadamard signs/scales against split activations. Only fires where the + // old code asserted, so no behavior change for loading models. + for (int ab = 0; ab < 2; ab++) { + const int ia = ab, ib = 1 - ab; + if (src_ss[ia].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED && + src_ss[ib].axis >= 0 && src_ss[ib].axis < GGML_MAX_DIMS) { + bool same_shape = true; + for (int dim = 0; dim < GGML_MAX_DIMS; dim++) { + const int64_t nd = tensor->src[ib]->ne[dim]; + const int64_t nm = tensor->src[ia]->ne[dim]; + if (nd != nm && nm != 1) { same_shape = false; break; } + } + if (same_shape) { + return src_ss[ib]; + } + } + } return handle_generic(src_ss, /*scalar_only =*/ false); }; diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 69fd86290cb8..c364b0c80a27 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -492,6 +492,32 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } } + // QWEN35 linear block: mirror SSM weights (replicated compute needs no sharding; + // residual stream is mirrored, verified by working Q4 tensor-split). + if (ud->model->arch == LLM_ARCH_QWEN35) { + if (std::regex_match(tensor_name, pattern_attn_gate_weight) || + std::regex_match(tensor_name, pattern_ssm_out_weight) || + std::regex_match(tensor_name, pattern_qkv_weight) || + std::regex_match(tensor_name, pattern_ssm_conv1d) || + std::regex_match(tensor_name, pattern_ssm_dt) || + std::regex_match(tensor_name, pattern_ssm_a) || + std::regex_match(tensor_name, pattern_ssm_alpha) || + std::regex_match(tensor_name, pattern_ssm_beta) || + std::regex_match(tensor_name, pattern_ssm_beta_alpha) || + 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_MIRRORED); + } + } + // Bonsai output head mirror: replicated output projection (logits identical on + // both devices, no gather order issues). PQ2_0-gated so Q4 models are untouched. + { + static const std::regex pattern_out_mir("^output\\.weight$"); + if (std::regex_match(tensor_name, pattern_out_mir) && + (tensor->type == GGML_TYPE_PQ2_0 || tensor->type == GGML_TYPE_PTQ1_0)) { + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); + } + } // Bonsai FFN mirror: PQ2_0-family FFN projections run replicated (their Hadamard // input prep [1024,34] cannot shard under even ratios; replication is exact math). // Q4 and other types untouched -> no regression for standard models. @@ -508,11 +534,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str { static const std::regex pattern_signs("^prism\\.hadamard\\.signs\\.[0-9]+$"); if (std::regex_match(tensor_name, pattern_signs)) { - // signs follow their (split) activation shards elementwise; - // 6144-wide signs serve axis-0-split SSM activations under even ratios - if (tensor->ne[0] == 6144) { - return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); - } + // sign vectors replicate; elementwise consumers broadcast them return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } } From e916733c5611b51494e6c97b90373c446f6962fc Mon Sep 17 00:00:00 2001 From: Sleipnir Date: Wed, 23 Sep 2026 18:38:36 +0000 Subject: [PATCH 4/7] tensor-split: split FFN weights + matching hadamard signs split; mirror attention block. PPL 1.0323 (baseline 1.0321) --- src/llama-model.cpp | 68 +++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/src/llama-model.cpp b/src/llama-model.cpp index c364b0c80a27..b51350023695 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -509,6 +509,23 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } } + // Bonsai (hadamard-folded PQ2_0): 1-D sign vectors feed the hadamard input + // transform and must carry the SAME split state as the activation they + // multiply. signs.6144 is consumed by BOTH the attention-output activation + // and the (mirrored) SSM output; one vector cannot serve a split and a + // mirrored consumer at once. Keep the small attention block mirrored and + // split the large FFN instead. + if (!ud->model->hadamard_sign_data.empty() && + (std::regex_match(tensor_name, pattern_q_weight) || + std::regex_match(tensor_name, pattern_kv_weight) || + std::regex_match(tensor_name, pattern_q_bias) || + std::regex_match(tensor_name, pattern_kv_bias) || + std::regex_match(tensor_name, pattern_qk_norm) || + std::regex_match(tensor_name, pattern_attn_out_weight) || + std::regex_match(tensor_name, pattern_attn_out_bias) || + std::regex_match(tensor_name, pattern_kv_cache))) { + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); + } // Bonsai output head mirror: replicated output projection (logits identical on // both devices, no gather order issues). PQ2_0-gated so Q4 models are untouched. { @@ -518,23 +535,19 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } } - // Bonsai FFN mirror: PQ2_0-family FFN projections run replicated (their Hadamard - // input prep [1024,34] cannot shard under even ratios; replication is exact math). - // Q4 and other types untouched -> no regression for standard models. - { - static const std::regex pattern_ffn_mir("blk\\.\\d*\\.ffn_(up|gate|down)\\.weight"); - if (std::regex_match(tensor_name, pattern_ffn_mir) && - (tensor->type == GGML_TYPE_PQ2_0 || tensor->type == GGML_TYPE_PTQ1_0)) { - return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); - } - } - // Hadamard sign vectors: match their consumers (per width). - // 17408-wide signs serve split FFN activations -> split; 6144-wide serve - // mirrored SSM activations -> mirrored. + // FFN is split (hadamard input is 1024-block aligned; signs.17408 splits with it). + // Hadamard sign vectors: a 1-D signs vector must carry the SAME split state + // as the activation it multiplies (the elementwise mul indexes from the + // device-local offset). The FFN input activation (ffn_swiglu, width 17408) + // is split axis-0, so its signs split too; every other width is consumed by + // mirrored activations and stays mirrored. { - static const std::regex pattern_signs("^prism\\.hadamard\\.signs\\.[0-9]+$"); - if (std::regex_match(tensor_name, pattern_signs)) { - // sign vectors replicate; elementwise consumers broadcast them + static const std::string signs_prefix = "prism.hadamard.signs."; + if (tensor_name.compare(0, signs_prefix.size(), signs_prefix) == 0) { + const long sign_width = std::stol(tensor_name.substr(signs_prefix.size())); + if (sign_width == 17408) { + return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); + } return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } } @@ -795,6 +808,14 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } // FFN + // Hadamard sign vectors: use the same 1024-block granularity as the FFN + // hadamard input so the elementwise-mul split state equals ffn_swiglu's. + { + static const std::string signs_pfx = "prism.hadamard.signs."; + if (tensor_name.compare(0, signs_pfx.size(), signs_pfx) == 0) { + return {std::lcm(blck_size, 1024)}; + } + } if (std::regex_match(tensor_name, pattern_ffn_up_weight) || std::regex_match(tensor_name, pattern_ffn_up_bias) || std::regex_match(tensor_name, pattern_ffn_gate_weight) || std::regex_match(tensor_name, pattern_ffn_gate_bias) || std::regex_match(tensor_name, pattern_ffn_gate_up_weight) || @@ -802,7 +823,8 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str std::regex_match(tensor_name, pattern_ffn_up_shexp_weight) || std::regex_match(tensor_name, pattern_ffn_gate_shexp_weight) || std::regex_match(tensor_name, pattern_ffn_down_shexp_weight)) { - const int64_t blck_size_perf = std::lcm(blck_size, 128); + const int64_t blck_size_perf = (tensor->type == GGML_TYPE_PQ2_0 || tensor->type == GGML_TYPE_PTQ1_0) + ? std::lcm(blck_size, 1024) : std::lcm(blck_size, 128); GGML_ASSERT(segments.size() == 1); return {blck_size_perf}; } @@ -815,6 +837,18 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str ggml_backend_meta_split_state split_state; memset(&split_state, 0, sizeof(split_state)); tensor_config tc = get_tensor_config(); + // Hadamard FFN: force rotation 0 so every layer's ffn_swiglu split and the + // shared 1-D signs vector receive identical device boundaries (rotation would + // make odd/even layers disagree, which a single shared vector cannot satisfy). + if (!ud->model->hadamard_sign_data.empty()) { + static const std::string signs_pfx_rot = "prism.hadamard.signs."; + if (std::regex_match(tensor_name, pattern_ffn_up_weight) || + std::regex_match(tensor_name, pattern_ffn_gate_weight) || + std::regex_match(tensor_name, pattern_ffn_down_weight) || + tensor_name.compare(0, signs_pfx_rot.size(), signs_pfx_rot) == 0) { + tc.rotation = 0; + } + } split_state.axis = tc.axis; if (split_state.axis >= 0 && split_state.axis < GGML_MAX_DIMS) { const int64_t blck_size = ggml_blck_size(tc.tensor_axis_0->type); From d429f6f59920eeabf69768102e7a9a4002a65194 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Sep 2026 06:49:57 +0000 Subject: [PATCH 5/7] tensor-split: full SSM+attention split (qwen35). Fix ssm_out hadamard-regroup layout mismatch. PPL 1.0316 (baseline 1.0321); pp512 405 vs 242 single (+68%) --- ggml/src/ggml-backend-meta.cpp | 45 +++++++++++++++++++++++++++++----- src/llama-model.cpp | 14 ++++++++--- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/ggml/src/ggml-backend-meta.cpp b/ggml/src/ggml-backend-meta.cpp index f68c8ae8db9d..ae50b398dc78 100644 --- a/ggml/src/ggml-backend-meta.cpp +++ b/ggml/src/ggml-backend-meta.cpp @@ -608,8 +608,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_1 && src_ss[1].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED) { ggml_backend_meta_split_state ret = src_ss[0]; ret.axis = GGML_BACKEND_SPLIT_AXIS_0; - ret.nr[0] = 1; - ret.n_segments = 1; return ret; } if (src_ss[1].axis == GGML_BACKEND_SPLIT_AXIS_1 && src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED) { @@ -849,10 +847,14 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( auto handle_ssm_conv = [&](const std::vector & src_ss) -> ggml_backend_meta_split_state { if (src_ss[0].axis == src_ss[1].axis) { if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_0) { - return {GGML_BACKEND_SPLIT_AXIS_1, {0}, {1}, 1}; + ggml_backend_meta_split_state ret = src_ss[0]; + ret.axis = GGML_BACKEND_SPLIT_AXIS_1; + return ret; } if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_1) { - return {GGML_BACKEND_SPLIT_AXIS_0, {0}, {1}, 1}; + ggml_backend_meta_split_state ret = src_ss[0]; + ret.axis = GGML_BACKEND_SPLIT_AXIS_0; + return ret; } } return handle_generic(src_ss, /*scalar_only =*/ false); @@ -872,7 +874,25 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( // state shape is [S_v, S_v, H_v, n_seqs] (s0 only); the heads dim is its own axis 2, // so a head-aligned split on the input cache lands on axis 2 here. GGML_ASSERT(src_ss[5].axis == GGML_BACKEND_SPLIT_AXIS_2 || src_ss[5].axis == GGML_BACKEND_SPLIT_AXIS_1 || src_ss[5].axis == GGML_BACKEND_SPLIT_AXIS_0); - return {GGML_BACKEND_SPLIT_AXIS_0, {0}, {1}, 1}; + // qwen35 grouped-V: the GDN output is laid out as head_ratio blocks of n_k heads + // (device-local V groups). Preserve that grouping so the downstream ssm_out + // (segmented {key_dim, head_ratio}) matches its activation layout. + { + const int64_t gdn_head_dim = tensor->src[0]->ne[0]; + const int64_t gdn_n_k_heads = tensor->src[0]->ne[1]; + const size_t gdn_n_bufs = ggml_backend_meta_buffer_n_bufs(tensor->buffer); + ggml_backend_meta_split_state gdn_ret = {GGML_BACKEND_SPLIT_AXIS_0, {0}, {1}, 1}; + if (gdn_head_dim > 0 && gdn_n_k_heads > 0 && tensor->ne[0] % (gdn_head_dim * gdn_n_k_heads) == 0) { + const int64_t gdn_ratio = tensor->ne[0] / (gdn_head_dim * gdn_n_k_heads); + if (gdn_ratio > 1 && tensor->ne[0] % (int64_t)(gdn_n_bufs * gdn_ratio) == 0) { + gdn_ret.nr[0] = (uint32_t) gdn_ratio; + for (size_t j = 0; j < gdn_n_bufs; j++) { + gdn_ret.ne[j] = tensor->ne[0] / (int64_t)(gdn_n_bufs * gdn_ratio); + } + } + } + return gdn_ret; + } }; auto calculate_split_state = [&]() -> ggml_backend_meta_split_state { @@ -1111,7 +1131,8 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( split_state = {GGML_BACKEND_SPLIT_AXIS_UNKNOWN, {0}, {1}, 1}; } break; } - if (split_state.axis >= 0 && split_state.axis < GGML_MAX_DIMS) { + const bool split_ne_precomputed = split_state.nr[0] > 1 && split_state.ne[0] > 0; + if (split_state.axis >= 0 && split_state.axis < GGML_MAX_DIMS && !split_ne_precomputed) { bool first_src_split_by_axis = true; const size_t n_bufs = ggml_backend_meta_buffer_n_bufs(tensor->buffer); @@ -1120,7 +1141,19 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( continue; } if (first_src_split_by_axis) { + const bool seg_copy = src_ss[i].n_segments > 1 && + tensor->ne[split_state.axis] == tensor->src[i]->ne[src_ss[i].axis]; + if (seg_copy) { + split_state.n_segments = src_ss[i].n_segments; + for (size_t s = 0; s < src_ss[i].n_segments; s++) { + split_state.nr[s] = src_ss[i].nr[s]; + for (size_t j = 0; j < n_bufs; j++) { + split_state.ne[s*n_bufs + j] = src_ss[i].ne[s*n_bufs + j]; + } + } + } for (size_t j = 0; j < n_bufs; j++) { + if (seg_copy) break; // Take over ratio from src: for (size_t s = 0; s < src_ss[i].n_segments; s++) { split_state.ne[s*n_bufs + j] = 0; diff --git a/src/llama-model.cpp b/src/llama-model.cpp index b51350023695..5402de1218bd 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -494,7 +494,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str // QWEN35 linear block: mirror SSM weights (replicated compute needs no sharding; // residual stream is mirrored, verified by working Q4 tensor-split). - if (ud->model->arch == LLM_ARCH_QWEN35) { + if (false && ud->model->arch == LLM_ARCH_QWEN35) { if (std::regex_match(tensor_name, pattern_attn_gate_weight) || std::regex_match(tensor_name, pattern_ssm_out_weight) || std::regex_match(tensor_name, pattern_qkv_weight) || @@ -515,7 +515,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str // and the (mirrored) SSM output; one vector cannot serve a split and a // mirrored consumer at once. Keep the small attention block mirrored and // split the large FFN instead. - if (!ud->model->hadamard_sign_data.empty() && + if (false && !ud->model->hadamard_sign_data.empty() && (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight) || std::regex_match(tensor_name, pattern_q_bias) || @@ -545,7 +545,7 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str static const std::string signs_prefix = "prism.hadamard.signs."; if (tensor_name.compare(0, signs_prefix.size(), signs_prefix) == 0) { const long sign_width = std::stol(tensor_name.substr(signs_prefix.size())); - if (sign_width == 17408) { + if (sign_width == 17408 || sign_width == 6144) { return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); } return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); @@ -664,9 +664,15 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str GGML_ASSERT(tensor->ne[axis] == 2*key_dim + value_dim); return {{key_dim, 2 + head_ratio}}; } - if (std::regex_match(tensor_name, pattern_attn_gate_weight) || std::regex_match(tensor_name, pattern_ssm_out_weight)) { + if (std::regex_match(tensor_name, pattern_attn_gate_weight)) { return {{key_dim, head_ratio}}; } + if (std::regex_match(tensor_name, pattern_ssm_out_weight)) { + // ssm_out consumes the hadamard-regrouped (perm_rep) activation, whose + // device-local layout is a contiguous axis-0 half; keep it contiguous + // so the weight rows match the activation elements per device. + return {{tensor->ne[axis], 1}}; + } if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) || std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta)) { return {{n_k_heads, head_ratio}}; From 0563456f3c5865f990ac6f8b167941cc2d97d7b6 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Sep 2026 07:08:48 +0000 Subject: [PATCH 6/7] tensor-split: split PQ2_0 output head (mirror was unnecessary; PPL 1.0320) --- src/llama-model.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 5402de1218bd..832cd80f5a90 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -526,15 +526,6 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str std::regex_match(tensor_name, pattern_kv_cache))) { return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); } - // Bonsai output head mirror: replicated output projection (logits identical on - // both devices, no gather order issues). PQ2_0-gated so Q4 models are untouched. - { - static const std::regex pattern_out_mir("^output\\.weight$"); - if (std::regex_match(tensor_name, pattern_out_mir) && - (tensor->type == GGML_TYPE_PQ2_0 || tensor->type == GGML_TYPE_PTQ1_0)) { - return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); - } - } // FFN is split (hadamard input is 1024-block aligned; signs.17408 splits with it). // Hadamard sign vectors: a 1-D signs vector must carry the SAME split state // as the activation it multiplies (the elementwise mul indexes from the From dadcb4f2c994a6c4c247abeb57efb48cfeea100f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 24 Sep 2026 07:19:01 +0000 Subject: [PATCH 7/7] cleanup: remove debug SEG/DBG_SEG/DBG_GENERIC/DBG_CHAIN/DBG_SPLIT_FAIL/DBG_CONS stderr prints --- ggml/src/ggml-backend-meta.cpp | 34 ---------------------------------- src/llama-model.cpp | 9 --------- 2 files changed, 43 deletions(-) diff --git a/ggml/src/ggml-backend-meta.cpp b/ggml/src/ggml-backend-meta.cpp index ae50b398dc78..e8fce86cca13 100644 --- a/ggml/src/ggml-backend-meta.cpp +++ b/ggml/src/ggml-backend-meta.cpp @@ -534,14 +534,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( if (scalar_only && ret.axis >= 0 && ret.axis < GGML_MAX_DIMS) { ret = {GGML_BACKEND_SPLIT_AXIS_UNKNOWN, {0}, {1}, 1}; } - if (ret.axis == GGML_BACKEND_SPLIT_AXIS_UNKNOWN) { - fprintf(stderr, "DBG_GENERIC tensor='%s' op=%d", tensor->name, (int)tensor->op); - for (size_t k = 0; k < GGML_MAX_SRC; k++) { - if (tensor->src[k]) fprintf(stderr, " src%zu='%s'[ax=%d ne=%lld,%lld]", k, tensor->src[k]->name, - (int)src_ss[k].axis, (long long)src_ss[k].ne[0], (long long)src_ss[k].ne[1]); - } - fprintf(stderr, "\n"); - } GGML_ASSERT(ret.axis != GGML_BACKEND_SPLIT_AXIS_UNKNOWN); return ret; }; @@ -1164,18 +1156,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( split_state.ne[j] *= tensor->ne[split_state.axis]; if (split_state.ne[j] != 0 || tensor->src[i]->ne[src_ss[i].axis] != 0) { const int64_t div = tensor->src[i]->ne[src_ss[i].axis] * split_state.nr[0]; - if (div != 0 && split_state.ne[j] % div != 0) { - fprintf(stderr, "DBG_CHAIN:"); - { const ggml_tensor * c = tensor; for (int d = 0; d < 7 && c; d++) { - fprintf(stderr, " [%s op=%d]", c->name, (int)c->op); - c = c->src[0]; - } fprintf(stderr, "\n"); } - fprintf(stderr, "DBG_SPLIT_FAIL tensor='%s' op=%d axis=%d ne=[%lld,%lld,%lld,%lld] src='%s' src_axis=%d src_ne=%lld ne_j=%lld div=%lld nr0=%lld nbufs=%zu j=%zu srcop=%d srcsrc0op=%d srcseg=%zu srcne0=%lld srcne1=%lld srcnr0=%u srcnr1=%u\n", - tensor->name, (int)tensor->op, (int)split_state.axis, - (long long)tensor->ne[0], (long long)tensor->ne[1], (long long)tensor->ne[2], (long long)tensor->ne[3], - tensor->src[i]->name, (int)src_ss[i].axis, (long long)tensor->src[i]->ne[src_ss[i].axis], - (long long)split_state.ne[j], (long long)div, (long long)split_state.nr[0], n_bufs, j, (int)tensor->src[i]->op, tensor->src[i]->src[0] ? (int)tensor->src[i]->src[0]->op : -1, src_ss[i].n_segments, (long long)src_ss[i].ne[0], (long long)src_ss[i].ne[1], src_ss[i].nr[0], src_ss[i].nr[1]); - } GGML_ASSERT(div == 0 || split_state.ne[j] % div == 0); split_state.ne[j] /= div; } @@ -1188,20 +1168,6 @@ static struct ggml_backend_meta_split_state ggml_backend_meta_get_split_state( for (size_t s = 0; s < src_ss[i].n_segments; s++) { sum += src_ss[i].ne[s*n_bufs + j] * src_ss[i].nr[s]; } - if (!(split_state.ne[j]*split_state.nr[0] * tensor->src[i]->ne[src_ss[i].axis] - == sum * tensor->ne[split_state.axis])) { - fprintf(stderr, "DBG_CONS tensor='%s' op=%d ax=%d ne=[%lld,%lld] srci=%d src='%s' sax=%d sne=[%lld,%lld]\n", - tensor->name, (int)tensor->op, (int)split_state.axis, - (long long)split_state.ne[0], (long long)split_state.ne[1], (int)i, - tensor->src[i]->name, (int)src_ss[i].axis, - (long long)src_ss[i].ne[0], (long long)src_ss[i].ne[1]); - for (size_t k = 0; k < GGML_MAX_SRC; k++) { - if (tensor->src[k]) fprintf(stderr, " src%zu='%s' ax=%d ne=[%lld,%lld] nr0=%u nseg=%zu\n", - k, tensor->src[k]->name, (int)src_ss[k].axis, - (long long)src_ss[k].ne[0], (long long)src_ss[k].ne[1], - src_ss[k].nr[0], src_ss[k].n_segments); - } - } GGML_ASSERT(split_state.ne[j]*split_state.nr[0] * tensor->src[i]->ne[src_ss[i].axis] == sum * tensor->ne[split_state.axis]); } diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 832cd80f5a90..8b8ea9de5f9d 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -877,15 +877,6 @@ struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const str } split_state.ne[is*ud->n_devices + (j + tc.rotation) % ud->n_devices] = ne_s - low; split_state.nr[is] = nr_s; - fprintf(stderr, "SEG %s ax=%d ne=[%lld,%lld]\n", tensor_name.c_str(), - (int)split_state.axis, - (long long)split_state.ne[is*ud->n_devices+0], (long long)split_state.ne[is*ud->n_devices+1]); - if (tensor_name.find("ssm_out") != std::string::npos || tensor_name.find("attn_gate") != std::string::npos) { - fprintf(stderr, "DBG_SEG tensor=%s axis=%d ne_s=%lld nr_s=%u g=%lld ne=[%lld,%lld] rot=%d split=[%g,%g]\n", - tensor_name.c_str(), (int)split_state.axis, (long long)ne_s, nr_s, (long long)g_s, - (long long)split_state.ne[is*ud->n_devices+0], (long long)split_state.ne[is*ud->n_devices+1], - tc.rotation, tensor_split ? tensor_split[0] : -1.0f, tensor_split && ud->n_devices > 1 ? tensor_split[1] : -1.0f); - } } split_state.n_segments = segments.size(); } else {