<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>GPU Acceleration on KnightLi Blog</title>
        <link>https://www.knightli.com/en/tags/gpu-acceleration/</link>
        <description>Recent content in GPU Acceleration on KnightLi Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Sat, 09 May 2026 15:05:41 +0800</lastBuildDate><atom:link href="https://www.knightli.com/en/tags/gpu-acceleration/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>A Practical llama.cpp Multi-GPU Benchmarking Approach: Is 2x V100 16GB Faster Than One 32GB Card?</title>
        <link>https://www.knightli.com/en/2026/05/09/llama-cpp-multi-gpu-offload-performance/</link>
        <pubDate>Sat, 09 May 2026 15:05:41 +0800</pubDate>
        
        <guid>https://www.knightli.com/en/2026/05/09/llama-cpp-multi-gpu-offload-performance/</guid>
        <description>&lt;p&gt;Short version: llama.cpp multi-GPU offload is not free performance just because you add a second card. If the model already fits fully on one 32GB GPU, 2x V100 16GB is often less convenient than a single 32GB card and may even be slower. If the model does not fit on one 16GB card, the main value of dual GPUs is that the model can stay on GPU, and the benefit can be obvious.&lt;/p&gt;
&lt;h2 id=&#34;first-understand-split-mode&#34;&gt;First, Understand split mode
&lt;/h2&gt;&lt;p&gt;llama.cpp multi-GPU usage mainly revolves around &lt;code&gt;--split-mode&lt;/code&gt; and &lt;code&gt;--tensor-split&lt;/code&gt;. When discussing performance, distinguish these modes first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;layer&lt;/code&gt;: splits layers across GPUs. It is usually the most compatible starting point.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tensor&lt;/code&gt;: splits tensor computation across multiple GPUs. It is closer to true parallel compute, but depends more heavily on inter-GPU bandwidth and backend support.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;row&lt;/code&gt;: an older row-splitting mode that still appears in some setups, but is usually not the first choice for new deployments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In simple terms, &lt;code&gt;layer&lt;/code&gt; is like putting different floors on different cards. During single-token generation, it may not keep both cards fully busy at the same time. &lt;code&gt;tensor&lt;/code&gt; is more like letting both cards work on the same layer together. It has more theoretical parallelism, but inter-GPU communication can become the bottleneck.&lt;/p&gt;
&lt;h2 id=&#34;if-one-32gb-card-can-fit-the-model-dual-16gb-is-not-always-faster&#34;&gt;If One 32GB Card Can Fit the Model, Dual 16GB Is Not Always Faster
&lt;/h2&gt;&lt;p&gt;If the model and KV cache fit fully on one 32GB GPU, a single card is usually steadier and often faster. For hardware in the same generation, such as 1x V100 32GB versus 2x V100 16GB, the dual-card setup does not necessarily win.&lt;/p&gt;
&lt;p&gt;A conservative expectation is that 2x V100 16GB may be 10% to 40% slower than one V100 32GB, especially for single-user chat, Continue Agent, and code Q&amp;amp;A workloads where one request is mainly generating one answer.&lt;/p&gt;
&lt;p&gt;The reason is straightforward: multi-GPU does not simply merge VRAM into one fast pool. With layer splitting, inference moves across GPUs and one card may wait for the other during token generation. With tensor splitting, both cards can compute together, but intermediate results need cross-GPU synchronization, so bandwidth and latency directly affect throughput.&lt;/p&gt;
&lt;p&gt;So if your choice is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1x V100 32GB&lt;/li&gt;
&lt;li&gt;2x V100 16GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and the target model already fits fully on one 32GB card, the single 32GB card is often the more comfortable option.&lt;/p&gt;
&lt;h2 id=&#34;if-one-16gb-card-cannot-fit-the-model-dual-cards-matter&#34;&gt;If One 16GB Card Cannot Fit the Model, Dual Cards Matter
&lt;/h2&gt;&lt;p&gt;The situation changes completely when the model does not fit on one 16GB card but can fit across two 16GB cards.&lt;/p&gt;
&lt;p&gt;In that case, the value of dual GPUs is very direct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One 16GB card: may require heavy CPU offload, which can slow things down a lot.&lt;/li&gt;
&lt;li&gt;2x 16GB cards: weights can stay mostly on GPU, which may be much faster than mixed CPU/GPU execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this scenario, 2x V100 16GB is not guaranteed to beat one 32GB card, but it may be several times faster than a single 16GB card with heavy system-memory offload. In other words, the first value of dual cards is not acceleration. It is avoiding the need to push model weights into slower system RAM.&lt;/p&gt;
&lt;h2 id=&#34;v100-pcie-and-v100-sxm2-are-very-different&#34;&gt;V100 PCIe and V100 SXM2 Are Very Different
&lt;/h2&gt;&lt;p&gt;The easiest thing to overlook in multi-GPU inference is the interconnect.&lt;/p&gt;
&lt;p&gt;If you have V100 SXM2 with NVLink, cross-GPU communication bandwidth is much higher. NVIDIA&amp;rsquo;s V100 material lists NVLink interconnect bandwidth up to 300GB/s. In that environment, &lt;code&gt;tensor&lt;/code&gt; mode or higher-batch workloads have a better chance of approaching or exceeding single-card performance.&lt;/p&gt;
&lt;p&gt;If you have V100 PCIe, expectations should be more conservative. V100 PCIe mainly uses PCIe Gen3, and the listed interconnect bandwidth is 32GB/s. That is a very different class from NVLink, which is why dual PCIe cards often provide enough VRAM without doubling speed.&lt;/p&gt;
&lt;p&gt;So when judging whether 2x V100 16GB is worthwhile, do not only add the VRAM to 32GB. Also check whether the cards are PCIe or SXM2/NVLink.&lt;/p&gt;
&lt;h2 id=&#34;a-practical-buying-rule&#34;&gt;A Practical Buying Rule
&lt;/h2&gt;&lt;p&gt;If the model fits on one 32GB GPU, choose the single card first. Its latency, stability, and tuning cost are usually better.&lt;/p&gt;
&lt;p&gt;If the model does not fit on one 16GB GPU but can fit on two 16GB GPUs, dual cards are worth using. At that point, the goal is to keep weights on GPU as much as possible, not to expect linear performance scaling.&lt;/p&gt;
&lt;p&gt;If you have dual V100 PCIe cards, start with &lt;code&gt;--split-mode layer&lt;/code&gt; and aim for stable execution with less CPU fallback.&lt;/p&gt;
&lt;p&gt;If you have V100 SXM2/NVLink, it is more worth testing &lt;code&gt;tensor&lt;/code&gt;-related modes, especially for prefill, larger batches, or concurrent serving.&lt;/p&gt;
&lt;h2 id=&#34;when-to-buy-2x16gb-and-when-to-buy-1x32gb&#34;&gt;When to Buy 2x16GB and When to Buy 1x32GB
&lt;/h2&gt;&lt;p&gt;If you serve only one user and mainly do chat, code completion, Continue Agent, or long-context Q&amp;amp;A, and the target model fits within 32GB, 1x32GB is usually the better choice. It avoids cross-GPU scheduling, has steadier latency, and is easier to debug.&lt;/p&gt;
&lt;p&gt;If you already own one 16GB card and want a lower-cost path to run 30B, 32B, or higher-quantized models, 2x16GB makes sense. It may not double token/s, but it can keep weights on GPU that would otherwise require CPU offload.&lt;/p&gt;
&lt;p&gt;If you are buying from scratch, the priority can look like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single model, single user, latency-sensitive: prefer 1x32GB.&lt;/li&gt;
&lt;li&gt;Model does not fit on one card and budget is limited: consider 2x16GB.&lt;/li&gt;
&lt;li&gt;Machine has NVLink or SXM2: 2x16GB is much more interesting than ordinary PCIe dual cards.&lt;/li&gt;
&lt;li&gt;You want longer context later: do not only count model weights; reserve VRAM for KV cache too.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;practical-advice-for-layer-split-and-tensor-split&#34;&gt;Practical Advice for layer split and tensor split
&lt;/h2&gt;&lt;p&gt;The practical rule is: start with &lt;code&gt;layer&lt;/code&gt;, then benchmark &lt;code&gt;tensor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;layer&lt;/code&gt; is the default starting point. It splits the model by layer, has better compatibility, and is friendlier to PCIe dual-card systems. The downside is that generation can behave more like a pipeline: at certain moments one card is busy while the other waits.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tensor&lt;/code&gt; is better suited to machines with strong interconnects, such as V100 SXM2/NVLink. It splits part of the same layer&amp;rsquo;s computation across GPUs, so it has more parallelism in theory, but it also synchronizes across cards more often. On PCIe dual cards, communication overhead may eat the benefit.&lt;/p&gt;
&lt;p&gt;You can start with these tests:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt; --split-mode layer --tensor-split 1,1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt; --split-mode tensor --tensor-split 1,1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt; --split-mode layer --tensor-split 1,0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The third command is not meant as the long-term configuration. It gives you a single-card reference, so you can see whether dual GPUs are actually faster or only distributing VRAM pressure.&lt;/p&gt;
&lt;h2 id=&#34;why-prefill-and-decode-behave-differently&#34;&gt;Why prefill and decode Behave Differently
&lt;/h2&gt;&lt;p&gt;Local LLM performance should usually be viewed in two stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prefill&lt;/code&gt;: processes the input prompt. A typical metric is prompt-processing throughput such as &lt;code&gt;pp512&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;decode&lt;/code&gt;: generates the response token by token. A typical metric is token-generation throughput such as &lt;code&gt;tg128&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;prefill&lt;/code&gt; is more like large-batch matrix computation. With larger batches, it is easier to keep GPUs busy and more likely to benefit from multi-GPU parallelism. &lt;code&gt;decode&lt;/code&gt; generates one token after another. The batch is smaller and synchronization is more frequent, so cross-card communication and scheduling latency are easier to notice.&lt;/p&gt;
&lt;p&gt;That is why you may see dual GPUs improve &lt;code&gt;pp512&lt;/code&gt; while &lt;code&gt;tg128&lt;/code&gt; barely improves or even gets worse. For chat and agent workflows, user experience is closer to &lt;code&gt;tg128&lt;/code&gt;. For long document ingestion, batch prefill, or concurrent serving, &lt;code&gt;pp512&lt;/code&gt; also matters.&lt;/p&gt;
&lt;h2 id=&#34;can-kv-cache-become-a-second-vram-bottleneck&#34;&gt;Can KV cache Become a Second VRAM Bottleneck?
&lt;/h2&gt;&lt;p&gt;Yes. Many people only count model weights and forget KV cache.&lt;/p&gt;
&lt;p&gt;Model weights decide whether the model can load. KV cache decides whether you can use the context length you want. The longer the context, the higher the concurrency, and the larger the batch, the more visible KV cache usage becomes. You may find that the model itself fits in 32GB, but 32K or 64K context pushes VRAM over the limit.&lt;/p&gt;
&lt;p&gt;At minimum, leave VRAM headroom for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;KV cache&lt;/li&gt;
&lt;li&gt;CUDA graph or backend runtime overhead&lt;/li&gt;
&lt;li&gt;prompt batch and ubatch&lt;/li&gt;
&lt;li&gt;desktop, driver, and other process usage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you use 2x16GB, VRAM is not a fully equivalent 32GB pool. Some buffers, KV cache, or intermediate tensors may still be limited by remaining memory on a single card. When testing long context, use the target &lt;code&gt;--ctx-size&lt;/code&gt; and target concurrency directly instead of only checking whether the model starts.&lt;/p&gt;
&lt;h2 id=&#34;how-to-benchmark-dual-cards-with-llama-bench&#34;&gt;How to Benchmark Dual Cards with llama-bench
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;llama-bench&lt;/code&gt; is better than direct chatting for hardware comparison because it separates prompt processing and token generation into comparable metrics. The default example in the official README is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;llama-bench -m model.gguf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For dual V100 cards, test at least these sets:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Single-card baseline&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Dual-card layer split&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,1 llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt; --split-mode layer --tensor-split 1,1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Dual-card tensor split&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;0,1 llama-bench -m model.gguf -ngl &lt;span class=&#34;m&#34;&gt;99&lt;/span&gt; --split-mode tensor --tensor-split 1,1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Focus on two columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pp512&lt;/code&gt;: prompt processing, more relevant to long inputs and batch prefill.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tg128&lt;/code&gt;: token generation, more relevant to single-user chat and agent responsiveness.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep the model, quantization, context length, batch settings, driver version, and llama.cpp version fixed. Run each group several times and compare medians rather than one-off results. Finally, test your real workflow too, such as Continue Agent, an OpenAI-compatible server, or your own RAG requests, because a good benchmark does not always mean better interactive experience.&lt;/p&gt;
&lt;h2 id=&#34;one-sentence-conclusion&#34;&gt;One-Sentence Conclusion
&lt;/h2&gt;&lt;p&gt;The main advantage of 2x V100 16GB is VRAM capacity, not guaranteed generation speed. If the model fits on one card, a single 32GB card is usually faster and steadier. If the model does not fit on one 16GB card, dual 16GB cards become valuable because they avoid heavy CPU offload. Whether they are faster depends on split mode, batch size, model size, and whether the two V100 cards are connected through PCIe or NVLink.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;llama.cpp server README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.mintlify.com/ggml-org/llama.cpp/concepts/backends&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;llama.cpp Compute Backends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.nvidia.com/en-gb/data-center/tesla-v100/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;NVIDIA Tesla V100&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://images.nvidia.com/content/technologies/volta/pdf/tesla-volta-v100-datasheet.pdf&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;NVIDIA V100 Datasheet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
