Parallelize weight loading for weights targeted at anonymous host ram and also GPU#2057
Conversation
|
Additional commit with cuda implementation using the same threadpool
Big metal benchmarkSame system now loads kimi k2.7 in
Desktop Benchmarkson my home PC, I benchmarked loading time for
The result on desktop is totally inconclusive. It's the entire startup time for llama-server so lots of noise. I guess this is a speedup for large models on large machines, and no significant change with models for normal people. |
|
How can I test this? I observe zero difference in loading speed. Is it because my Threadripper 3995WX is a "desktop" rather than a server CPU? |
|
This is my current theory. My initial premise is that this work timing is neither dominated by CPU work (syscalls, memcpy), nor disk IO, so I determined that more threads is an easy win. On the big metal, more threads scales near linearly until disk saturation is reached at some ungodly speed (I could have taken a screenshot of On my desktop with
The benchmark I took was just measuring process startup to "model loading complete" message on stdout, so lots of room for noise. possible confounds are:
TLDR: If you do not have a huge model and a big raid 0, then expect no benefit. Otherwise, the cost is
edit: my memcpy is almost defintiely lower han yours 2channel DDR5 does not beat 8 channel DDR4. |
|
So my mental model for who wins is:
|
|
Sorry for the spam, but other meaningful change is that before, each thread had 4x1M buffers for async PCiE loading (2 for double buffering, and I think 2 for good luck (the extra buffers make no sense as-is)). I replaced them with 1x16M buffer per thread. This decreases cuda synchronization, and slightly drops pcie overhead. I did not benchmark, but it should be a win. Double buffering removed on the basis of "now there are threads". I suspect original author allocated 4 buffers to take advantage of more than one DMA queue, but afaik GGML will only use one anyway. |
4b15f21 to
5905d5d
Compare
5905d5d to
0f5482a
Compare
|
@ikawrakow Also parallelized loading for |
| if (read_buf.capacity() > n_size) { | ||
| read_buf = std::vector<no_init<uint8_t>>(); | ||
| } | ||
| read_buf.resize(n_size); |
There was a problem hiding this comment.
Unbounded memory use. Not mergeable.
|
@Farmadupe , loading my GLM 4.6 on my rig (265K, 192GB DDR5, 2x3090, 1xRTXA4000, SSD PCIE4, 155GB or so to load) went from 110-120s to 75-80s. Much appreciated, thank you, I was unable to do anything meaningful while playing with DIO implementations. |
|
I have a plan up my sleeve which should hopefully help with the fact that It's a bit brute force but the answer is just to.... ask the OS how much memory is available for allocation and then never exceed it. |
|
Funny that you speak about that. Being on Win11, 2 days ago, I had to make myself a custom pinmem implementation to get.. 54 GiB of pinned memory, with a tool to test the different allocations methods, and to test the WDDM and TCC drivers. But the transfer rate to RAM is still lame for now, even with your PR (2.5 GB/sec to host for now, while I now reach 3.5-4 GB/sec to the graphic cards, thanks to your implementation which tripled the speed of the Cuda tensors offloading (!). |

Another PR to speed up model loading. Another PR in a "spend my pocket money on runpod to spend less pocket money on runpod" series.
Observation
I'm playing with kimi k2.5/6/7 on cheap runpod nodes. Startup time is dominated by the "loading weights into buffer" phase. When I look at htop, cpu usage is low (25% of a single core), and disk read rate is moderate (0.7G/s -- big metal on runpod is often good for nearly 10G/s )
So, I observe:
Implementation
Existing loader function can load weights into 4 backend buffer kinds, all serialized loading. I am targeting the "load to host ram" path and my intention is to leave the rest alone.
Implementation is to extract main body of the loading function into a lambda, spawn a fixed number of threads for loading. To prevent changes in of behavior for the other three buffer "kinds" which I do not intent to parellelize, I added a mutex so that only one thread can progress at a time.
Design considerations
make_next_tensor.Benchmarks
AMD EPYC 7742 64-Core Processor8x Samsung Electronics Co Ltd NVMe SSD Controller PM173X479.57 GiBtodo
mmappath is not broken or regressed yet. I will do this.restpath. (is this for vulkan? sycl?) Anyway I declare that I did not break it because I don't know how to activate it.Thoughts
0.7G/sto5G/saverage. The Cuda load path is doing disk reads at0.7G/saverage, so maybe a similar speedup is available.16threads is faster than8on the big metal rig. I am scared to propose16because it sounds like a very high number, so this patch has8