-
Notifications
You must be signed in to change notification settings - Fork 41
llama: let an MTP draft borrow the target's embeddings and lm head #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: base/upstream-662a0b012
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1106,6 +1106,70 @@ bool llama_model_loader::lazy_read::add(const std::string & name, const ggml_ten | |
| return true; | ||
| } | ||
|
|
||
| // declared in llama-model.h, which this file does not include | ||
| const std::vector<std::pair<std::string, ggml_tensor *>> & llama_internal_get_tensor_map(const llama_model * model); | ||
|
|
||
| struct ggml_tensor * llama_model_loader::borrow_shared_tensor(const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne) { | ||
| // only the tensors a draft head is allowed to leave out, checked first so no other | ||
| // tensor in any model costs a metadata lookup | ||
| if (tn.tensor != LLM_TENSOR_TOKEN_EMBD && tn.tensor != LLM_TENSOR_OUTPUT && tn.tensor != LLM_TENSOR_OUTPUT_NORM) { | ||
| return nullptr; | ||
| } | ||
|
|
||
| // a file that ships the tensor keeps its own copy | ||
| const std::string name = tn.str(); | ||
| if (get_weight(name.c_str()) != nullptr) { | ||
| return nullptr; | ||
| } | ||
|
|
||
| // a draft that left out its embeddings left out the whole set, so the missing token_embd is | ||
| // what marks one. without this an arch that ties the head to its own token_embd when | ||
| // output.weight is absent (qwen3.5, qwen3-next) would borrow the target's head instead | ||
| if (get_weight("token_embd.weight") != nullptr) { | ||
| return nullptr; | ||
| } | ||
|
|
||
| if (model_shared == nullptr) { | ||
| throw std::runtime_error(format("%s: missing tensor '%s'; if this is a draft head that shares " | ||
| "the target's embeddings, load it as a draft of its target model, not on its own", | ||
| __func__, name.c_str())); | ||
| } | ||
|
|
||
| ggml_tensor * src = nullptr; | ||
| for (const auto & [n, t] : llama_internal_get_tensor_map(model_shared)) { | ||
| if (n == name) { | ||
| src = t; | ||
| break; | ||
| } | ||
| } | ||
| if (src == nullptr) { | ||
| throw std::runtime_error(format("%s: draft needs tensor '%s' from the target, which does not have it", | ||
| __func__, name.c_str())); | ||
|
Comment on lines
+1145
to
+1147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the target has tied embeddings and omits Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| // the draft uses the tensor directly, so the shapes must agree exactly | ||
| size_t dim = 0; | ||
| for (const int64_t n : ne) { | ||
| if (dim >= GGML_MAX_DIMS || src->ne[dim] != n) { | ||
| throw std::runtime_error(format("%s: draft and target disagree on '%s': target has %s, draft wants %s", | ||
| __func__, name.c_str(), llama_format_tensor_shape(src).c_str(), llama_format_tensor_shape(ne).c_str())); | ||
| } | ||
| dim++; | ||
| } | ||
| for (; dim < GGML_MAX_DIMS; dim++) { | ||
| if (src->ne[dim] != 1) { | ||
| throw std::runtime_error(format("%s: draft and target disagree on '%s': target has %s, draft wants %s", | ||
| __func__, name.c_str(), llama_format_tensor_shape(src).c_str(), llama_format_tensor_shape(ne).c_str())); | ||
| } | ||
| } | ||
|
|
||
| LLAMA_LOG_INFO("%s: tensor %s taken from the target model\n", __func__, name.c_str()); | ||
|
|
||
| // not counted in n_created or size_data: the tensor is not in this file and is neither | ||
| // allocated nor freed here | ||
| return src; | ||
| } | ||
|
|
||
| struct ggml_tensor * llama_model_loader::create_tensor( | ||
| const llama_hparams & hparams, const buft_list_t * buft_list_cpu, const buft_list_t * buft_list_input, const buft_list_t * buft_list_output, | ||
| const buft_list_t * buft_list_layer, const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) { | ||
|
|
@@ -1326,6 +1390,12 @@ struct ggml_tensor * llama_model_loader::create_tensor( | |
| return ret; | ||
| } | ||
|
|
||
| // must run before check_tensor_dims: the tensor is absent from this file by design, and for | ||
| // the lm head it must also win over the arch fallback that ties the head to token_embd | ||
| if (ggml_tensor * shared = borrow_shared_tensor(tn, ne)) { | ||
| return shared; | ||
| } | ||
|
|
||
| LLAMA_LOG_DEBUG("%s: loading tensor %s\n", __func__, tn.str().c_str()); | ||
| const struct ggml_tensor * cur = check_tensor_dims(tn.str(), ne, !(flags & TENSOR_NOT_REQUIRED), flags & TENSOR_ALLOW_RESHAPE); | ||
| if (cur == NULL) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This enables
mtp_shared_embdfor every architecture supporting--mtp, but several such exporters still unconditionally retain their shared tensors: DeepSeek V3.2/V4, HY V3, Nemotron-H, and Step3.5 have unchangedmtp_onlykeep lists that do not inspect this flag. For those inputs,--mtp-shared-embdsucceeds and writes the shared-target metadata but produces essentially the ordinary large sidecar, contrary to the CLI promise. Either update each supported filter or reject this option for exporters that do not implement stripping.Useful? React with 👍 / 👎.