Skip to content

Commit d82983f

Browse files
MarkShark2claude
andcommitted
wip: glm5next pipedec body/head graphs, separate draft-file loading, indexer seq_trim
Snapshot before replacing the ggml-org#27752-based glm5next port with upstream PR ggml-org#27773. Kept for reference: the last-layer output_norm/output duplicates that keep the trunk graph on the pipeline when the head is pinned to --device-draft, the PIPEDEC_BODY/HEAD graph split, the mtp_only/trunk_only probes for a draft-only GGUF, load_mtp gating for --model-draft, and llama_memory_hybrid_idx::seq_trim. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PtrnaBuYDHGDvy73TRkFbG
1 parent f329e5d commit d82983f

6 files changed

Lines changed: 128 additions & 21 deletions

File tree

common/common.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,8 @@ common_init_result::common_init_result(common_params & params, bool model_only)
13071307
auto cparams_dft = common_context_params_to_llama(params_dft);
13081308
if (spec_mtp) {
13091309
cparams_dft.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
1310+
// a separate draft file is the MTP block itself
1311+
mparams_dft.load_mtp = true;
13101312
}
13111313
cparams_dft.n_rs_seq = 0;
13121314

@@ -1748,7 +1750,11 @@ struct llama_model_params common_model_params_to_llama(common_params & params) {
17481750
mparams.progress_callback = params.load_progress_callback;
17491751
mparams.progress_callback_user_data = params.load_progress_callback_user_data;
17501752
mparams.no_alloc = params.no_alloc;
1751-
mparams.load_mtp = common_spec_has_mtp(params.speculative.types);
1753+
// [fork] with a separate draft file (--model-draft) the MTP block lives there, so the
1754+
// target must not materialize its own copy - glm5next ships blk.45 at Q8_0, 7.4 GiB
1755+
// that would otherwise land on --device-draft. The draft load re-enables the flag.
1756+
mparams.load_mtp = common_spec_has_mtp(params.speculative.types) &&
1757+
!params.speculative.has_dft();
17521758

17531759
return mparams;
17541760
}

common/speculative.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2695,6 +2695,8 @@ common_speculative_init_result::common_speculative_init_result(
26952695

26962696
if (spec_mtp) {
26972697
cparams.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
2698+
// a separate draft file is the MTP block itself; the target skipped its copy
2699+
mparams.load_mtp = true;
26982700
}
26992701

27002702
// the draft context holds as many tokens per sequence as the target context

src/llama-memory-hybrid-idx.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,20 @@ void llama_memory_hybrid_idx::seq_cp(llama_seq_id seq_id_src, llama_seq_id seq_i
185185
}
186186
}
187187

188+
bool llama_memory_hybrid_idx::seq_trim(llama_seq_id seq_id, llama_pos p0) {
189+
// [fork, PipeDec tree] the indexer cache mirrors the attention cache's slot layout,
190+
// so a branch trim that leaves it untouched drifts the two apart
191+
if (!llama_memory_hybrid::seq_trim(seq_id, p0)) {
192+
return false;
193+
}
194+
195+
if (mem_idx) {
196+
mem_idx->seq_rm(seq_id, p0, -1);
197+
}
198+
199+
return true;
200+
}
201+
188202
void llama_memory_hybrid_idx::seq_keep(llama_seq_id seq_id) {
189203
llama_memory_hybrid::seq_keep(seq_id);
190204

src/llama-memory-hybrid-idx.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class llama_memory_hybrid_idx : public llama_memory_hybrid {
6565

6666
bool seq_rm (llama_seq_id seq_id, llama_pos p0, llama_pos p1) override;
6767
void seq_cp (llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) override;
68+
bool seq_trim(llama_seq_id seq_id, llama_pos p0) override;
6869
void seq_keep(llama_seq_id seq_id) override;
6970
void seq_add (llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) override;
7071
void seq_div (llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) override;

src/models/glm5next.cpp

Lines changed: 92 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader & ml) {
176176
mtp_flags |= TENSOR_SKIP;
177177
}
178178

179+
// [fork] the draft head may also ship as its own GGUF (--model-draft + --device-draft):
180+
// blk.<n_layer>.* plus token_embd / output / output_norm and none of the trunk, so it
181+
// can be quantized apart from the trunk and pinned to the draft GPU. Both halves
182+
// declare nextn in their metadata, so probe for the tensors as qwen4exp does.
183+
const bool mtp_only = (n_layer_nextn > 0) && (ml.get_weight("blk.0.attn_norm.weight") == nullptr);
184+
const int trunk_flags = mtp_only ? TENSOR_NOT_REQUIRED : 0;
185+
179186
tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);
180187
output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);
181188
output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);
@@ -184,55 +191,71 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader & ml) {
184191
}
185192
// note: no hc_head_*, the hyper-connection head is a plain mean
186193

194+
// [fork, PipeDec] when the output head is pinned to the draft GPU
195+
// (--device-draft), the output norm and lm_head would follow it off the
196+
// pipeline. A graph only pipelines if it ends on an RPC backend: a local
197+
// tail makes every split-input copy fall back to synchronize + blocking
198+
// copy, which drains the endpoint inside the submission of the current
199+
// graph. Duplicate both onto the last transformer layer and use them in
200+
// the trunk/body graphs (see graph) so the whole trunk stays on the
201+
// pipeline, exactly as when there is no drafter at all. The deferred
202+
// head graph keeps the originals on the draft GPU by design.
203+
if (!mtp_only && params.mtp_dev != nullptr && n_layer > 0) {
204+
output_norm_trunk = create_tensor_on_layer(ml, tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd},
205+
TENSOR_NOT_REQUIRED | TENSOR_DUPLICATED, n_layer - 1);
206+
output_trunk = create_tensor_on_layer(ml, tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab},
207+
TENSOR_NOT_REQUIRED | TENSOR_DUPLICATED, n_layer - 1);
208+
}
209+
187210
for (int i = 0; i < n_layer_all; ++i) {
188211
auto & layer = layers[i];
189212

190213
const bool is_mtp = i >= n_layer;
191214
const bool is_recr = !is_mtp && hparams.is_recr(i);
192-
const int flags = is_mtp ? mtp_flags : 0;
215+
const int flags = is_mtp ? mtp_flags : trunk_flags;
193216

194217
layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, flags);
195218
layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, flags);
196219

197220
// hyper-connections wrap the trunk blocks only, the MTP block uses plain residuals
198221
if (!is_mtp) {
199-
layer.hc_attn_fn = create_tensor(tn(LLM_TENSOR_HC_ATTN_FN, "weight", i), {hc_dim, hc_mix_dim}, 0);
200-
layer.hc_attn_base = create_tensor(tn(LLM_TENSOR_HC_ATTN_BASE, "weight", i), {hc_mix_dim}, 0);
201-
layer.hc_attn_scale = create_tensor(tn(LLM_TENSOR_HC_ATTN_SCALE, "weight", i), {3}, 0);
202-
layer.hc_ffn_fn = create_tensor(tn(LLM_TENSOR_HC_FFN_FN, "weight", i), {hc_dim, hc_mix_dim}, 0);
203-
layer.hc_ffn_base = create_tensor(tn(LLM_TENSOR_HC_FFN_BASE, "weight", i), {hc_mix_dim}, 0);
204-
layer.hc_ffn_scale = create_tensor(tn(LLM_TENSOR_HC_FFN_SCALE, "weight", i), {3}, 0);
222+
layer.hc_attn_fn = create_tensor(tn(LLM_TENSOR_HC_ATTN_FN, "weight", i), {hc_dim, hc_mix_dim}, trunk_flags);
223+
layer.hc_attn_base = create_tensor(tn(LLM_TENSOR_HC_ATTN_BASE, "weight", i), {hc_mix_dim}, trunk_flags);
224+
layer.hc_attn_scale = create_tensor(tn(LLM_TENSOR_HC_ATTN_SCALE, "weight", i), {3}, trunk_flags);
225+
layer.hc_ffn_fn = create_tensor(tn(LLM_TENSOR_HC_FFN_FN, "weight", i), {hc_dim, hc_mix_dim}, trunk_flags);
226+
layer.hc_ffn_base = create_tensor(tn(LLM_TENSOR_HC_FFN_BASE, "weight", i), {hc_mix_dim}, trunk_flags);
227+
layer.hc_ffn_scale = create_tensor(tn(LLM_TENSOR_HC_FFN_SCALE, "weight", i), {3}, trunk_flags);
205228
}
206229

207230
if (is_recr) {
208231
// conv1d may be stored 4D [d_conv, 1, d_inner, 1] or 3D (quantization drops the trailing 1)
209232
auto conv = [&](llm_tensor tid) {
210233
ggml_tensor * t = create_tensor(tn(tid, "weight", i), {d_conv, 1, d_inner, 1}, TENSOR_NOT_REQUIRED);
211-
return t ? t : create_tensor(tn(tid, "weight", i), {d_conv, 1, d_inner}, 0);
234+
return t ? t : create_tensor(tn(tid, "weight", i), {d_conv, 1, d_inner}, trunk_flags);
212235
};
213236
layer.ssm_q_conv = conv(LLM_TENSOR_SSM_CONV1D_Q);
214237
layer.ssm_k_conv = conv(LLM_TENSOR_SSM_CONV1D_K);
215238
layer.ssm_v_conv = conv(LLM_TENSOR_SSM_CONV1D_V);
216239

217-
create_tensor_qkv(layer, i, n_embd, d_inner, d_inner, d_inner, 0);
240+
create_tensor_qkv(layer, i, n_embd, d_inner, d_inner, d_inner, trunk_flags);
218241

219-
layer.ssm_f_a = create_tensor(tn(LLM_TENSOR_SSM_F_A, "weight", i), {n_embd, head_dim}, 0);
220-
layer.ssm_f_b = create_tensor(tn(LLM_TENSOR_SSM_F_B, "weight", i), {head_dim, d_inner}, 0);
221-
layer.ssm_g_a = create_tensor(tn(LLM_TENSOR_SSM_G_A, "weight", i), {n_embd, head_dim}, 0);
222-
layer.ssm_g_b = create_tensor(tn(LLM_TENSOR_SSM_G_B, "weight", i), {head_dim, d_inner}, 0);
223-
layer.ssm_beta = create_tensor(tn(LLM_TENSOR_SSM_BETA, "weight", i), {n_embd, n_head}, 0);
242+
layer.ssm_f_a = create_tensor(tn(LLM_TENSOR_SSM_F_A, "weight", i), {n_embd, head_dim}, trunk_flags);
243+
layer.ssm_f_b = create_tensor(tn(LLM_TENSOR_SSM_F_B, "weight", i), {head_dim, d_inner}, trunk_flags);
244+
layer.ssm_g_a = create_tensor(tn(LLM_TENSOR_SSM_G_A, "weight", i), {n_embd, head_dim}, trunk_flags);
245+
layer.ssm_g_b = create_tensor(tn(LLM_TENSOR_SSM_G_B, "weight", i), {head_dim, d_inner}, trunk_flags);
246+
layer.ssm_beta = create_tensor(tn(LLM_TENSOR_SSM_BETA, "weight", i), {n_embd, n_head}, trunk_flags);
224247

225248
// ssm_a holds -exp(A_log), folded at conversion time (kimi-linear/kimi-k3 convention)
226-
layer.ssm_a = create_tensor(tn(LLM_TENSOR_SSM_A, i), {n_head}, 0);
249+
layer.ssm_a = create_tensor(tn(LLM_TENSOR_SSM_A, i), {n_head}, trunk_flags);
227250

228251
// some converters emit dt_bias under the default ".weight" suffix
229252
layer.ssm_dt_b = create_tensor(tn(LLM_TENSOR_SSM_DT, "bias", i), {d_inner}, TENSOR_NOT_REQUIRED);
230253
if (!layer.ssm_dt_b) {
231-
layer.ssm_dt_b = create_tensor(tn(LLM_TENSOR_SSM_DT, "weight", i), {d_inner}, 0);
254+
layer.ssm_dt_b = create_tensor(tn(LLM_TENSOR_SSM_DT, "weight", i), {d_inner}, trunk_flags);
232255
}
233256

234-
layer.ssm_o_norm = create_tensor(tn(LLM_TENSOR_SSM_NORM, "weight", i), {head_dim}, 0);
235-
layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {d_inner, n_embd}, 0);
257+
layer.ssm_o_norm = create_tensor(tn(LLM_TENSOR_SSM_NORM, "weight", i), {head_dim}, trunk_flags);
258+
layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {d_inner, n_embd}, trunk_flags);
236259
} else {
237260
layer.wq_a = create_tensor(tn(LLM_TENSOR_ATTN_Q_A, "weight", i), {n_embd, q_lora_rank}, flags);
238261
layer.attn_q_a_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_A_NORM, "weight", i), {q_lora_rank}, flags);
@@ -292,6 +315,10 @@ void llama_model_glm5next::load_arch_tensors(llama_model_loader & ml) {
292315
}
293316

294317
std::unique_ptr<llm_graph_context> llama_model_glm5next::build_arch_graph(const llm_graph_params & params) const {
318+
if (params.gtype == LLM_GRAPH_TYPE_DECODER_PIPEDEC_HEAD) {
319+
return std::make_unique<graph_pipedec_head>(*this, params);
320+
}
321+
295322
if (params.gtype == LLM_GRAPH_TYPE_DECODER_MTP) {
296323
return std::make_unique<graph_mtp>(*this, params);
297324
}
@@ -991,7 +1018,13 @@ llama_model_glm5next::graph::graph(const llama_model & model, const llm_graph_pa
9911018
// the k-pool metadata is the same for every full layer, so build it once
9921019
auto * inp_kpool = build_inp_kpool(inp_hyb);
9931020

994-
ggml_tensor * inp_out_ids = build_inp_out_ids();
1021+
// The PipeDec body returns every unmasked hidden row directly and never
1022+
// gathers output rows. Do not register an unused out_ids input: unused
1023+
// graph inputs are intentionally not allocated, but set_inputs() would
1024+
// still attempt to populate one if it were registered here.
1025+
ggml_tensor * inp_out_ids = params.gtype == LLM_GRAPH_TYPE_DECODER_PIPEDEC_BODY
1026+
? nullptr
1027+
: build_inp_out_ids();
9951028

9961029
// inputs_embeds.unsqueeze(2).expand(-1, -1, hc, -1)
9971030
ggml_tensor * inpL = ggml_reshape_3d(ctx0, inp, n_embd, 1, n_tokens);
@@ -1053,19 +1086,58 @@ llama_model_glm5next::graph::graph(const llama_model & model, const llm_graph_pa
10531086
ggml_tensor * cur = glm5next_hc_mean(ctx0, inpL);
10541087
cb(cur, "hc_head", -1);
10551088

1056-
cur = build_norm(cur, model.output_norm, nullptr, LLM_NORM_RMS, -1);
1089+
const auto & model_glm = static_cast<const llama_model_glm5next &>(model);
1090+
1091+
cur = build_norm(cur, model_glm.output_norm_trunk ? model_glm.output_norm_trunk : model.output_norm,
1092+
nullptr, LLM_NORM_RMS, -1);
10571093

10581094
// post-norm hidden state feeds the NextN/MTP draft head
10591095
cb(cur, "h_nextn", -1);
10601096
res->t_h_nextn = cur;
10611097

1098+
// [fork, PipeDec] stage 2 queues token-sized trunk graphs across the layer
1099+
// devices. The body ends at the post-output-norm hidden state; those rows
1100+
// are copied out and fed to one batched graph_pipedec_head (lm_head only)
1101+
// after the body pipeline drains. Stopping here also keeps the body graph
1102+
// on the pipeline: the lm_head would otherwise drag the tail to the draft
1103+
// GPU and end the graph on a local backend.
1104+
if (params.gtype == LLM_GRAPH_TYPE_DECODER_PIPEDEC_BODY) {
1105+
ggml_build_forward_expand(gf, cur);
1106+
return;
1107+
}
1108+
10621109
if (inp_out_ids && narrow_late) {
10631110
cur = ggml_get_rows(ctx0, cur, inp_out_ids);
10641111
}
10651112

10661113
cb(cur, "result_norm", -1);
10671114
res->t_embd = cur;
10681115

1116+
cur = ggml_mul_mat(ctx0, model_glm.output_trunk ? model_glm.output_trunk : model.output, cur);
1117+
cb(cur, "result_output", -1);
1118+
res->t_logits = cur;
1119+
1120+
ggml_build_forward_expand(gf, cur);
1121+
}
1122+
1123+
// Deferred PipeDec verification head: the body lanes stop at the post-norm
1124+
// hidden state, so all that is left here is the lm_head over the batched rows
1125+
// gathered from every lane in the group. Runs on the draft GPU by design.
1126+
llama_model_glm5next::graph_pipedec_head::graph_pipedec_head(
1127+
const llama_model & model, const llm_graph_params & params)
1128+
: llm_graph_context(params) {
1129+
auto inp = std::make_unique<llm_graph_input_embd>(hparams.n_embd);
1130+
1131+
inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd, n_tokens);
1132+
ggml_set_input(inp->embd);
1133+
cb(inp->embd, "pipedec_h_input", -1);
1134+
1135+
ggml_tensor * cur = inp->embd;
1136+
res->add_input(std::move(inp));
1137+
1138+
cb(cur, "result_norm", -1);
1139+
res->t_embd = cur;
1140+
10691141
cur = ggml_mul_mat(ctx0, model.output, cur);
10701142
cb(cur, "result_output", -1);
10711143
res->t_logits = cur;

src/models/models.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,12 @@ struct llama_model_glm5next : public llama_model_base {
13871387
void load_arch_hparams(llama_model_loader & ml) override;
13881388
void load_arch_tensors(llama_model_loader & ml) override;
13891389

1390+
// [fork, PipeDec] last-layer copies of the tail tensors, created only when
1391+
// the output head is pinned to the draft GPU (--device-draft), so the trunk
1392+
// and body graphs end on the pipeline instead of the local backend
1393+
ggml_tensor * output_norm_trunk = nullptr;
1394+
ggml_tensor * output_trunk = nullptr;
1395+
13901396
struct graph : public llm_build_delta_net_base {
13911397
graph(const llama_model & model, const llm_graph_params & params);
13921398

@@ -1433,6 +1439,12 @@ struct llama_model_glm5next : public llama_model_base {
14331439
graph_mtp(const llama_model & model, const llm_graph_params & params);
14341440
};
14351441

1442+
// deferred PipeDec verification head: lm_head over rows already normed by
1443+
// the body lanes; runs on the draft GPU where the head tensors live
1444+
struct graph_pipedec_head : public llm_graph_context {
1445+
graph_pipedec_head(const llama_model & model, const llm_graph_params & params);
1446+
};
1447+
14361448
std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;
14371449
};
14381450

0 commit comments

Comments
 (0)