Deepseek V4: split-mode tensor#25860
Conversation
|
I'm working on sm tensor for this model, and this PR is not an acceptable version. |
i know it's not acceptable to be merged, i just wanted to share, as a Proof of concept / comparison vs layer split mode. I can close this if it's totally useless for you guys. |
|
refactored with gpt-sol to remove the logging and free some more vram. performance stayed the same |
|
On 4x 3090s
|
|
Did some tests as well (8x3090)
|
JohannesGaessler
left a comment
There was a problem hiding this comment.
For context: @am17an asked me for advice regarding -sm tensor for DS4 and I am commenting on this PR since I think it will be helpful for his purposes. I do not want you to feed these comments to a language model to try and get a working implementation, I will not review and possibly merge that since I very much expect it to be more work than doing the implementation myself.
| if (src_ss[0].axis == src_ss[1].axis && src_ss[0].axis >= GGML_BACKEND_SPLIT_AXIS_2 && | ||
| src_ss[0].axis < GGML_MAX_DIMS) { | ||
| GGML_ASSERT(split_states_equal(src_ss[0], src_ss[1])); | ||
| return src_ss[0]; | ||
| } |
There was a problem hiding this comment.
This is incorrect. The only valid axis that would be covered here is PARTIAL and there is no sensible way to combine two PARTIAL tensors via a matrix multiplication since (a + b) * (a + b) == a*a + 2*a*b + b*b.
| if (src_ss[0].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED) { | ||
| GGML_ASSERT(src_ss[1].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED); | ||
| GGML_ASSERT(src_ss[2].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED); | ||
| GGML_ASSERT(tensor->src[4] == nullptr || src_ss[4].axis == GGML_BACKEND_SPLIT_AXIS_MIRRORED); | ||
| return {GGML_BACKEND_SPLIT_AXIS_MIRRORED, {0}, {1}, 1}; | ||
| } |
There was a problem hiding this comment.
This is indicative of incorrect assignments in llama-model.cpp. It is in principle possible to just run everything as mirrored but for an expensive operation like FA that is going to massively gimp the performance.
| if (std::regex_match(tensor_name, pattern_kv_cache) || | ||
| std::regex_match(tensor_name, pattern_dsv4_state)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED); | ||
| } |
There was a problem hiding this comment.
This is 100% the wrong way to do it and just a workaround in lieu of the correct logic.
| if (std::regex_match(tensor_name, pattern_attn_sinks)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output_a.weight"); | ||
| } | ||
| if (std::regex_match(tensor_name, pattern_attn_q_b_weight)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output_a.weight"); | ||
| } | ||
| if (std::regex_match(tensor_name, pattern_attn_out_a_weight)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output_b.weight"); | ||
| } | ||
| if (std::regex_match(tensor_name, pattern_attn_out_b_weight)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0); | ||
| } | ||
| 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"); | ||
| } | ||
| if (std::regex_match(tensor_name, pattern_ffn_down_shexp_weight)) { | ||
| return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ffn_down_shexp.weight"); | ||
| } |
There was a problem hiding this comment.
I'd have to check against the compute graph (and also spin up the model with a debugger) but these may be correct. The bottom line is that attention heads should be split by GPU and combined once at the end. Mirroring them will result in duplicated KV cache and bad performance.
| if (ud->model->arch == LLM_ARCH_DEEPSEEK4) { | ||
| return {1}; | ||
| } |
There was a problem hiding this comment.
This is wrong unless the model does not use GQA at all.
| if (std::regex_match(tensor_name, pattern_attn_q_b_weight)) { | ||
| GGML_ASSERT(segments.size() == 1); | ||
| return {hparams.n_embd_head_k(il)}; | ||
| } |
There was a problem hiding this comment.
This seems wrong to me but I don't know off the top of my head what the correct implementation would be.
Overview
I have made Deepseek V4 flash (DeepSeek-V4-Flash-GGUF/ UD-Q8_K_XL) work with tensor-split tensor, on my 8x3090 setup.
Played with some books content processing and asking the LLM about the contents ( 2 questions 16k context first question, 40k second one) and the TG/PP greatly improved
prefill speed (~150–175% faster), generation speed (~24–29% faster)
Before (layer split mode):
1'st prompt:
2'nd prompt:
After ( tensor split mode)
1'st prompt:
2'nd prompt:
Additional information
I have not made this to be merged as i realized it was butchered by the AI (debug code + i guess modified in the wrong places), but maybe someone who knows more about the project can do a proper version as this has good potential i think.
The downside to this is that the KV cache is still copied on all gpus and atm on 192gb vram i can only have 100k context because of that. ( i have tried to vibe code the 1m context part, to split the context on multiple gpus and not copy it, but it wrecked the TG to 5tps, so i just left that out)
@fairydreaming @ggerganov @am17an maybe one of you has time to look at this. ( sorry for tagging)
Requirements