Skip to content

server : add speculative decoding support - #10455

Merged
ggerganov merged 2 commits into
masterfrom
gg/speculative-server
Nov 25, 2024
Merged

ggerganov merged 2 commits into
masterfrom
gg/speculative-server

Conversation

@ggerganov

@ggerganov ggerganov commented Nov 22, 2024

Copy link
Copy Markdown
Member

target #10362

Initial implementation that enables speculative decoding in llama-server. Test with this command:

./bin/llama-server \
    -m  ../models/qwen2.5-32b-coder-instruct/ggml-model-q8_0.gguf \
    -md ../models/qwen2.5-0.5b-coder-instruct/ggml-model-q4_0.gguf \
    -ngl 99 -ngld 99 -fa --port 8033 -c 32768 \
    --draft-max 16 --draft-min 5

Feedback is appreciated.

TODO:

  • simplify
  • control draft context size
  • rename server.params to something else to avoid confusions
  • test multi-user
  • test offloading draft model with RPC

@3Simplex

3Simplex commented Nov 22, 2024

Copy link
Copy Markdown

From what I have read the goal is faster inference while retaining quality of the larger model.

I am using rx6900xt with vulkan
Using Qwen2.5-Coder-7B-Instruct-Q8_0.gguf alone I see 50 t/s
Using Qwen2.5-Coder-0.5B-Instruct-Q8_0.gguf alone I see 230 t/s

I get about 10-12 t/s with an incorrect configuration.

  • .\llama-server.exe -m "...Qwen2.5-Coder-0.5B-Instruct-Q8_0.gguf" -md "...Qwen2.5-Coder-7B-Instruct-Q8_0.gguf" -ngl 99 -ngld 99 -fa --port 8080 -c 32768 --draft 10 --draft-min 5

Flipping the models increased speed and the output looks similar. This makes sense since the -md is the draft model which is supposed to be the smaller model.

I get about 16 t/s with the correct configuration.

  • .\llama-server.exe -m "...Qwen2.5-Coder-7B-Instruct-Q8_0.gguf" -md "...Qwen2.5-Coder-0.5B-Instruct-Q8_0.gguf" -ngl 99 -ngld 99 -fa --port 8080 -c 32768 --draft 10 --draft-min 5

Setting a lower context 2048, when the limit is reached the server crashed.

@ggerganov
ggerganov force-pushed the gg/speculative-server branch 5 times, most recently from c5ddee2 to e80f758 Compare November 24, 2024 15:09
@ggerganov
ggerganov marked this pull request as ready for review November 24, 2024 15:11
@ggerganov

Copy link
Copy Markdown
Member Author

@3Simplex What is the output of the following bench on your machine:

llama-bench.exe -m "...Qwen2.5-Coder-7B-Instruct-Q8_0.gguf" -p 1,1,2,3,4,5,6,7,8,12,16,32 -r 20 -n 0 -ngl 99 -fa 1

@3Simplex

Copy link
Copy Markdown

@ggerganov

.\llama-bench.exe -m "...\Qwen2.5-Coder-7B-Instruct-Q8_0.gguf" -p 1,1,2,3,4,5,6,7,8,12,16,32 -r 20 -n 0 -ngl 99 -fa 1
ggml_vulkan: Found 1 Vulkan devices:
ggml_vulkan: 0 = AMD Radeon RX 6900 XT (AMD proprietary driver) | uma: 0 | fp16: 1 | warp size: 64

model size params backend ngl fa test t/s
ggml_vulkan: Compiling shaders..............................Done!
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp1 37.79 ± 0.30
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp1 37.81 ± 0.29
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp2 16.14 ± 0.04
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp3 23.40 ± 0.06
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp4 31.10 ± 0.04
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp5 37.39 ± 1.74
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp6 45.52 ± 0.06
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp7 51.53 ± 0.09
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp8 58.57 ± 0.28
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp12 80.38 ± 0.13
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp16 105.83 ± 0.54
qwen2 7B Q8_0 7.54 GiB 7.62 B Vulkan 99 1 pp32 202.53 ± 0.21

build: 0c745909 (4160)

@mostlygeek

Copy link
Copy Markdown
Contributor

I tried out commit e80f758e6e4063e0867a5a8d2c929dac48432ede with my P40s, 3xP40s and 3090. These are the commands for the baselines and the tests.

Baseline:

./llama-server -m /mnt/nvme/models/Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf -ngl 99 -ngld 99 -fa --port 9999 -c 4096 --draft-max 16 --draft-min 5

With speculative model (just removed the -md model.gguf):

./llama-server -m /mnt/nvme/models/Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf -md /mnt/nvme/models/Qwen2.5-Coder-0.5B-Instruct-Q4_K_M.gguf -ngl 99 -ngld 99 -fa --port 9999 -c 4096 --draft-max 16 --draft-min 5

Tested it with curl using:

for n in seq 1 5; do curl http://10.0.1.50:9999/v1/chat/completions -N -v -d '{"messages":[{"role":"user","content":"write hello world in golang"}],"temperature":0.1, "stream":false,"max_tokens":1000, "model":"coder" }'; done

Data:

GPU baseline pp baseline eval -md ... pp -md ... eval
3090 299 tps 34 tps 300 tps 31 tps
P40 101 tps 11.22 tps 101 tps 10.52 tps
3xP40 91 tps 10.6 tps 90 tps 9.8 tps

@ggerganov
ggerganov force-pushed the gg/speculative-server branch from e80f758 to d905266 Compare November 24, 2024 19:59
@ggerganov

ggerganov commented Nov 24, 2024

Copy link
Copy Markdown
Member Author

Currently, it requires cache_prompt: true to be set do speculation. Will be fixed in next PRs. Using greedy sampling should improve things as well:

cache_prompt: true, top_k: 1, samplers: ["top_k"]

The biggest benefit from speculative sampling is when you have more grounding. For example, if you have enough memory for a bigger context, you can try something like this:

# get the llama.vim plugin source code
code=$(curl -s https://raw.githubusercontent.com/ggml-org/llama.vim/refs/heads/master/autoload/llama.vim | jq -sRr @json)

# ask qwen to implement something (speculative decoding disabled)
curl --request POST --url http://localhost:8033/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer no-key" -d "$(jq -n --arg code "$code" \
  '{ messages: [{ role: "system", content: "You are an expert computer scientist. Respond only with code blocks. Do not add any other comments except code." }, { role: "user", content: "Suggest an improvement for the `chunk_sim` function using Levenstein distance: ```\($code)```" }], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 0 }')" | jq -r .choices[0].message.content

# speculative decoding enabled
curl --request POST --url http://localhost:8033/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer no-key" -d "$(jq -n --arg code "$code" \
  '{ messages: [{ role: "system", content: "You are an expert computer scientist. Respond only with code blocks. Do not add any other comments except code." }, { role: "user", content: "Suggest an improvement for the `chunk_sim` function using Levenstein distance: ```\($code)```" }], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 16 }')" | jq -r .choices[0].message.content

With CUDA, you might want to try setting "speculative.n_min": 0 or 1 since I think it has efficient small-batch kernels for Q4_K, so no need to skip the small batches.

@mostlygeek

mostlygeek commented Nov 24, 2024

Copy link
Copy Markdown
Contributor

Thank you for the guidance. Using d905266b05918c709269604b73fe50b41820e965, I reran the tests.

Results look quite good.

GPU n_max:0 n_max:16 change
P40 8.7 tps 39.4 tps 4.45x
3xP40 -sm row 12.70 tps 53 tps 4.17x
3090 29 tps 167 tps 5.73x

Server command:

./llama-server -m /mnt/nvme/models/Qwen2.5-Coder-32B-Instruct-Q4_K_M.gguf -md /mnt/nvme/models/Qwen2.5-Coder-0.5B-Instruct-Q4_K_M.gguf -ngl 99 -ngld 99 -fa --port 9999 -c 10240 --draft-max 16 --draft-min 0 --host 0.0.0.0 2>&1 | grep 'eval time'

Kept this pretty consistent, except for the 3xP40 run where I added -sm row

Client side:

$ code=$(curl -s https://raw.githubusercontent.com/ggml-org/llama.vim/refs/heads/master/autoload/llama.vim | jq -sRr @json)

$ for n in `seq 1 5`; \
do \
    curl --request POST --url http://10.0.1.50:9999/v1/chat/completions \
        -H "Content-Type: application/json" -H "Authorization: Bearer no-key" \
        -d "$(jq -n --arg code "$code" '{ messages: [{ role: "system", content: "You are an expert computer scientist. Respond only with code blocks. Do not add any other comments except code." }, { role: "user", content: "Suggest an improvement for the `chunk_sim` function using Levenstein distance: ```\($code)```" }], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 16 }')" | jq -r .choices[0].message.content; \
done

For the client side curl, I changed speculative.n_max between 0 and 16 to get the different timings.

Here are the raw results. Some observations first:

  • with n_max: 0, 437 tokens were generated. With n_max: 16, 440 tokens were generated.
  • the server was restarted between tests to clear the cache
  • the code generated was identical (ran it through a diff)

3090 data

# speculative.n_max: 0
prompt eval time =    8032.34 ms /  8318 tokens (    0.97 ms per token,  1035.56 tokens per second)
       eval time =   14975.84 ms /   437 tokens (   34.27 ms per token,    29.18 tokens per second)
prompt eval time =      37.56 ms /     1 tokens (   37.56 ms per token,    26.62 tokens per second)
       eval time =   14988.71 ms /   437 tokens (   34.30 ms per token,    29.16 tokens per second)
prompt eval time =      37.15 ms /     1 tokens (   37.15 ms per token,    26.92 tokens per second)
       eval time =   15005.60 ms /   437 tokens (   34.34 ms per token,    29.12 tokens per second)
prompt eval time =      37.27 ms /     1 tokens (   37.27 ms per token,    26.83 tokens per second)
       eval time =   15017.94 ms /   437 tokens (   34.37 ms per token,    29.10 tokens per second)
prompt eval time =      37.49 ms /     1 tokens (   37.49 ms per token,    26.67 tokens per second)
       eval time =   15026.50 ms /   437 tokens (   34.39 ms per token,    29.08 tokens per second)

# speculative.n_max: 16
prompt eval time =    7915.24 ms /  8318 tokens (    0.95 ms per token,  1050.88 tokens per second)
       eval time =    9432.51 ms /   440 tokens (   21.44 ms per token,    46.65 tokens per second)
prompt eval time =      38.44 ms /     1 tokens (   38.44 ms per token,    26.02 tokens per second)
       eval time =    2626.82 ms /   440 tokens (    5.97 ms per token,   167.50 tokens per second)
prompt eval time =      37.93 ms /     1 tokens (   37.93 ms per token,    26.37 tokens per second)
       eval time =    2629.31 ms /   440 tokens (    5.98 ms per token,   167.34 tokens per second)
prompt eval time =      37.91 ms /     1 tokens (   37.91 ms per token,    26.38 tokens per second)
       eval time =    2628.70 ms /   440 tokens (    5.97 ms per token,   167.38 tokens per second)
prompt eval time =      38.20 ms /     1 tokens (   38.20 ms per token,    26.18 tokens per second)
       eval time =    2637.09 ms /   440 tokens (    5.99 ms per token,   166.85 tokens per second)

single P40

# speculative.n_max: 0
prompt eval time =   55669.14 ms /  8318 tokens (    6.69 ms per token,   149.42 tokens per second)
       eval time =   50050.73 ms /   437 tokens (  114.53 ms per token,     8.73 tokens per second)
prompt eval time =     114.98 ms /     1 tokens (  114.98 ms per token,     8.70 tokens per second)
       eval time =   50075.91 ms /   437 tokens (  114.59 ms per token,     8.73 tokens per second)
prompt eval time =     113.24 ms /     1 tokens (  113.24 ms per token,     8.83 tokens per second)
       eval time =   50097.56 ms /   437 tokens (  114.64 ms per token,     8.72 tokens per second)
       
# speculative.n_max: 16
prompt eval time =   55362.42 ms /  8318 tokens (    6.66 ms per token,   150.25 tokens per second)
       eval time =   29859.49 ms /   440 tokens (   67.86 ms per token,    14.74 tokens per second)
prompt eval time =     113.02 ms /     1 tokens (  113.02 ms per token,     8.85 tokens per second)
       eval time =   11146.53 ms /   440 tokens (   25.33 ms per token,    39.47 tokens per second)
prompt eval time =     113.75 ms /     1 tokens (  113.75 ms per token,     8.79 tokens per second)
       eval time =   11142.33 ms /   440 tokens (   25.32 ms per token,    39.49 tokens per second)
prompt eval time =     113.19 ms /     1 tokens (  113.19 ms per token,     8.83 tokens per second)
       eval time =   11175.47 ms /   440 tokens (   25.40 ms per token,    39.37 tokens per second)
prompt eval time =     112.65 ms /     1 tokens (  112.65 ms per token,     8.88 tokens per second)
       eval time =   11159.70 ms /   440 tokens (   25.36 ms per token,    39.43 tokens per second)

3xP40 (-sm row)

# speculative.n_max: 0
prompt eval time =   36909.28 ms /  8318 tokens (    4.44 ms per token,   225.36 tokens per second)
       eval time =   34412.92 ms /   437 tokens (   78.75 ms per token,    12.70 tokens per second)
prompt eval time =      79.49 ms /     1 tokens (   79.49 ms per token,    12.58 tokens per second)
       eval time =   34414.53 ms /   437 tokens (   78.75 ms per token,    12.70 tokens per second)
prompt eval time =      79.40 ms /     1 tokens (   79.40 ms per token,    12.60 tokens per second)
       eval time =   34413.66 ms /   437 tokens (   78.75 ms per token,    12.70 tokens per second)

# speculative.n_max: 16
prompt eval time =   36858.25 ms /  8318 tokens (    4.43 ms per token,   225.68 tokens per second)
       eval time =   27168.81 ms /   440 tokens (   61.75 ms per token,    16.20 tokens per second)
prompt eval time =      79.72 ms /     1 tokens (   79.72 ms per token,    12.54 tokens per second)
       eval time =    8290.25 ms /   440 tokens (   18.84 ms per token,    53.07 tokens per second)
prompt eval time =      79.73 ms /     1 tokens (   79.73 ms per token,    12.54 tokens per second)
       eval time =    8295.16 ms /   440 tokens (   18.85 ms per token,    53.04 tokens per second)
prompt eval time =      79.99 ms /     1 tokens (   79.99 ms per token,    12.50 tokens per second)
       eval time =    8295.91 ms /   440 tokens (   18.85 ms per token,    53.04 tokens per second)
prompt eval time =      79.88 ms /     1 tokens (   79.88 ms per token,    12.52 tokens per second)
       eval time =    8301.95 ms /   440 tokens (   18.87 ms per token,    53.00 tokens per second)

Code generated:

function! s:chunk_sim(c0, c1)
    let l:lines0 = join(a:c0, "\n")
    let l:lines1 = join(a:c1, "\n")

    let l:distance = levenshtein(l:lines0, l:lines1)

    return 1 - (l:distance / max([strlen(l:lines0), strlen(l:lines1)]))
endfunction

function! levenshtein(s1, s2)
    let l:len1 = strlen(a:s1)
    let l:len2 = strlen(a:s2)

    if l:len1 == 0
        return l:len2
    endif

    if l:len2 == 0
        return l:len1
    endif

    let l:dp = []
    for i in range(l:len1 + 1)
        call add(l:dp, [])
        for j in range(l:len2 + 1)
            call add(l:dp[i], 0)
        endfor
    endfor

    for i in range(l:len1 + 1)
        let l:dp[i][0] = i
    endfor

    for j in range(l:len2 + 1)
        let l:dp[0][j] = j
    endfor

    for i in range(1, l:len1 + 1)
        for j in range(1, l:len2 + 1)
            let l:cost = (strcharpart(a:s1, i - 1, 1) == strcharpart(a:s2, j - 1, 1)) ? 0 : 1
            let l:dp[i][j] = min([l:dp[i - 1][j] + 1, l:dp[i][j - 1] + 1, l:dp[i - 1][j - 1] + l:cost])
        endfor
    endfor

    return l:dp[l:len1][l:len2]
endfunction

@mostlygeek

mostlygeek commented Nov 24, 2024

Copy link
Copy Markdown
Contributor

Also, is 0 and 16 the only valid values for speculative.n_max? I tried it with 4, 12, and got this error: common/common.cpp:1480: GGML_ASSERT(batch.seq_id[batch.n_tokens] && "llama_batch size exceeded") failed

@ggerganov

ggerganov commented Nov 24, 2024

Copy link
Copy Markdown
Member Author

Thanks for the detailed tests. The results are inflated because there is one tricky side effect from the caching - consecutive runs with the same prompt will reuse the previous draft context which combined with greedy sampling would make the drafting instantaneous. So basically, in the following data for example, only the first result is relevant:

# speculative.n_max: 16
prompt eval time =    7915.24 ms /  8318 tokens (    0.95 ms per token,  1050.88 tokens per second)
       eval time =    9432.51 ms /   440 tokens (   21.44 ms per token,    46.65 tokens per second)    <--- only this is relevant
prompt eval time =      38.44 ms /     1 tokens (   38.44 ms per token,    26.02 tokens per second)
       eval time =    2626.82 ms /   440 tokens (    5.97 ms per token,   167.50 tokens per second)
prompt eval time =      37.93 ms /     1 tokens (   37.93 ms per token,    26.37 tokens per second)
       eval time =    2629.31 ms /   440 tokens (    5.98 ms per token,   167.34 tokens per second)
prompt eval time =      37.91 ms /     1 tokens (   37.91 ms per token,    26.38 tokens per second)
       eval time =    2628.70 ms /   440 tokens (    5.97 ms per token,   167.38 tokens per second)
prompt eval time =      38.20 ms /     1 tokens (   38.20 ms per token,    26.18 tokens per second)
       eval time =    2637.09 ms /   440 tokens (    5.99 ms per token,   166.85 tokens per second)

i.e. 46.65 t/s. The next runs are reusing the drafts and are not representative.

Also, is 0 and 16 the only valid values for speculative.n_max? I tried it with 4, 12, and got this error: common/common.cpp:1480: GGML_ASSERT(batch.seq_id[batch.n_tokens] && "llama_batch size exceeded") failed

This was a bug - it is fixed now. You should be able to change n_max to any value. Btw, for CUDA it might make sense to set n_min to 0 or 1 and keep n_max ~ 16. But feel free to experiment.

Btw, here is another fun test that I came up with which uses less context and is suitable for speculation:

# get top 10 stories from Hacker News
hn=$(curl -s https://hacker-news.firebaseio.com/v0/topstories.json | jq -r '.[:10] | @tsv' | tr '\t' '\n' | xargs -I {} curl -s "https://hacker-news.firebaseio.com/v0/item/{}.json" | jq -sRr @json)

# make a Markdown table based on some criteria
curl --request POST --url http://localhost:8033/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer no-key" -d "$(jq -n --arg hn "$hn" \
  '{ messages: [{ role: "system", content: "You are a helpful text-editing assistant. Respond only with the requested text. Do not add any other comments to your response." }, { role: "user", content: "Extract a Markdown table that contains only stories about software engineering, AI or machine learning from the front-page of HN. The table should include: author, title, score, comments and an URL to the story: ```\($hn)```." }], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 16 }')" | jq -r .choices[0].message.content

@mostlygeek

Copy link
Copy Markdown
Contributor

i.e. 46.65 t/s. The next runs are reusing the drafts and are not representative.

Thanks. That seems a lot more realistic.

I did some tests with a much shorter prompt: "write snake game in swift"

GPU n_max:0 n_max:16 change
P40 10.54 tps 17.11 tps 1.62x
3xP40 -sm row 16.22 tps 22.80 tps 1.4x
3090 34.78 tps 51.31 tps 1.47x

curl --request POST --url http://10.0.1.50:9999/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer no-key" -d "$(jq -n --arg code "$code" '{ messages: [{ role: "system", content: "You are an expert computer scientist. Respond only with code blocks. Do not add any other comments except code." }, { role: "user", content: "write snake game in swift"}], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 0 }')" | jq -r .choices[0].message.content;

@ggerganov

Copy link
Copy Markdown
Member Author

These numbers look reasonable. The speedup can vary in both ways based on the inputs, but enabling speculative should almost never result in slower than normal decoding.

@3Simplex

Copy link
Copy Markdown

These numbers look reasonable. The speedup can vary in both ways based on the inputs, but enabling speculative should almost never result in slower than normal decoding.

With this build I am up to 25t/s on first run generation with speculative decoding using 15/5 draft tokens.

@mostlygeek

mostlygeek commented Nov 25, 2024

Copy link
Copy Markdown
Contributor

A bit of data with llama-3.1 70B and llama-3.2 1B as the draft model. Prompt: "write a story about the natural resources in Canada".

GPU n_max:0 n_max:16 change
3xP40 -sm row 9.80 tps 12.27 tps 1.25x

Server:

$ ./llama-server -m /mnt/nvme/models/Meta-Llama-3.1-70B-Instruct-Q4_K_L.gguf \
-md /mnt/nvme/models/Llama-3.2-1B-Instruct-Q4_K_M.gguf \
-ngl 99 -ngld 99 -fa --port 9999 -c 10240 --draft-max 16 --draft-min 1 \
--host 0.0.0.0 -sm row

client (changed speculative.n_max between 0 and 16)

$ curl --request POST --url http://10.0.1.50:9999/v1/chat/completions \
-d "$(jq -n --arg code "$code" '{ messages: [{ role: "system", content: "You are a helpful AI."}, {role: "user",content: "write a story about the natural resources in Canada"}], cache_prompt: true, top_k: 1, samplers: ["top_k"], "speculative.n_max": 0 }')" \
| jq -r .choices[0].message.content;

@ggerganov

Copy link
Copy Markdown
Member Author

Note that I am not very sure what happens with multiple GPUs, but it is possible that the draft model gets split across them, which is not desired (see the logs if that is the case). You would want to keep the draft model fully on one GPU.

Base automatically changed from gg/speculative-refactor to master November 25, 2024 07:58
@ggerganov
ggerganov force-pushed the gg/speculative-server branch from c277c4d to 156aa6d Compare November 25, 2024 08:05
@sorasoras

Copy link
Copy Markdown

Note that I am not very sure what happens with multiple GPUs, but it is possible that the draft model gets split across them, which is not desired (see the logs if that is the case). You would want to keep the draft model fully on one GPU.

I wonder if it is possible to load draft and main model onto different backend. Ie a 7900xtx and P40 in a -cb process

@ggerganov
ggerganov merged commit 9ca2e67 into master Nov 25, 2024
@firelex

firelex commented Dec 20, 2024

Copy link
Copy Markdown

Thanks, @ggerganov. I'm on the latest build.

I use this command:

./build/bin/llama-server -m ../models/verification.gguf -md ../models/drafting.gguf --port 8033 --ctx-size 4096 --cache-reuse 256 -ub 4096 -b 4096 -ngl 99 -ngld 99 -fa -lv 1 -dt 0.1 -t 1 --draft-max 16 --draft-min 5

And this on the client:

"top_k": 1, "samplers": ["top_k"]

And the result varies quite a bit - from slower to same to a little faster than just running the Q4 K-M 70bn directly. I copy some of the logging below. I can't read the logs, but from what I can tell, the speculative decoding part is working, but the draft_candidates have a lot of 0s and 1s, which I assume are probabilities - is this how it should be?

Hmmm... the zero/one probabilities I got when I ran a 4-bit 8bn drafting model that was fine-tuned on the same data as the verification model. If I use a non-finetuned 1bn or 3bn model, the probabilities vary more, but still no sustained speed-up (just sometimes). Next step: Fine-tune the 1bn model. That shoudl give me more speed than the 8bn bu thten hopefully better proability distributions.

@firelex

firelex commented Dec 21, 2024

Copy link
Copy Markdown

BTW, @ggerganov: From what I can tell, llama-server doesn't support multi-slot KV caches right now, allowing different prompts to maintain separate caches simultaneously. Is that right? This feature would go a long way to speeding up function calling on Metal. Potentially with disk offloading, although I suspect most agentic apps should get by on a dozen different base prompts. I've seen references to this for llama-cli, but I don't think llama-server supports this. I've asked one of my team members to look into this and make a proposal for adding it.

Okay, so this is already addresse (and solved) here: #9135

@firelex

firelex commented Dec 25, 2024

Copy link
Copy Markdown

@ggerganov - I've finally got some good news to report. As reported previously, speculative decoding had almost no effect on my M4 Max. I reliably got 8.7 t/s for Llama 3.3 70bn with a 3bn drafting model. But when I started to crank up the -np value, t/s went up significantly. It maxed out at -np 7, when I reliably got 16+ t/s (a doubling of what I had originally, which is amazing). When I went to -np 8, performance cratered and I ended up with 7 t/s.

Cranking up the -np value when I run JUST the 70bn model with a drafting model has no effect, so it's definitely an enabler for speculative decoding, at least on my setup.

Next up: Getting KV caching to work. With no -np parameter, the system prompt gets cached. With the -np parameter, I see no such effect. I will now try to force the server to use pre-populated KV slots. Will report back when I have that working.

@firelex

firelex commented Dec 25, 2024

Copy link
Copy Markdown

I'm confused. Shouldn't the number of slots available for KV caching be independent of whatever causes spec. decoding to speed up when I set -np to 6 or 7?

@saood06

saood06 commented Apr 8, 2025

Copy link
Copy Markdown

What models/model sizes/quants are you using

-m  .\gemma-2-27B-it-Q4_K_M-fp16.gguf ^ [18 GB]
-md .\gemma-2-2b-it-IQ3_XXS.gguf ^ [1.18 GB]

I feel that the 2B model may not be able to run fast enough on the CPU. This causing a bottleneck.

I know I'm fairly late but you may have had better results if your -md was gemma-2-2b-it-Q4_0.gguf

Seunghhon pushed a commit to Seunghhon/llama.cpp that referenced this pull request Apr 26, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
Seunghhon pushed a commit to Seunghhon/llama.cpp that referenced this pull request Apr 26, 2026
ljubomirj pushed a commit to ljubomirj/llama.cpp that referenced this pull request May 6, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
ljubomirj pushed a commit to ljubomirj/llama.cpp that referenced this pull request May 6, 2026
my-other-github-account pushed a commit to my-other-github-account/llama.cpp that referenced this pull request May 15, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
my-other-github-account pushed a commit to my-other-github-account/llama.cpp that referenced this pull request May 15, 2026
AlexiAlp pushed a commit to minghaop/llama.cpp that referenced this pull request Jun 2, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
AlexiAlp pushed a commit to minghaop/llama.cpp that referenced this pull request Jun 2, 2026
AlexiAlp pushed a commit to minghaop/llama.cpp that referenced this pull request Jun 2, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
AlexiAlp pushed a commit to minghaop/llama.cpp that referenced this pull request Jun 2, 2026
fukuro-kun pushed a commit to fukuro-kun/fukuro-llama-cpp-turboquant that referenced this pull request Jul 5, 2026
* server : add speculative decoding support

ggml-ci

* server : add helper function slot.can_speculate()

ggml-ci
fukuro-kun pushed a commit to fukuro-kun/fukuro-llama-cpp-turboquant that referenced this pull request Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.