С приходом нейросетей теперь можно решать задачи гораздо проще, правда и задачи бы такой не стояло не будь нейросетей и llama.cpp, и huggingface. В общем смысл статьи в том чтобы попытаться математически вычислить теоретическую скорость генерации токенов в программе llama.cpp для моделей на hugginface, просто открыв карточку модели и нажав на закладку (букмарклет). Вот как это выглядит:
На самом деле этих калькуляторов в интернете довольно много, но почему-то у меня они показывают совсем не то что я вижу в реальности. Опять же, для меня было открытие что я могу на своей RTX3070 8Gb запускать MOE модели до 35 млрд параметров на довольно неплохой скорости, просто потому что все нейронки в чатах говорят что все, конец - 8 Гб не хватит. Я думаю что они остановились в своем развитии где-то год назад и все еще частенько вспоминают про модели llama или mistral, что сейчас уже не совсем актуально, по крайней мере для использования, может для научных изысканий эти модели будут лучше современных.
В общем-то и в этом калькуляторе нет никаких новых идей, он работает как и все остальные - вычисляет скорость исходя из пропускной способности видеопамяти пользователя. Но как выяснилось просто поделить одно число на другое (вес модели на пропускную способность видеокарты) не совсем правильно, когда я сказал что в реале у меня другая скорость, нейронки стали вводить поправочные коэффициент в виде эффективности видеокарты (60-80%). Я подозреваю что это на самом деле имеет место быть - каждая видео карта настроена по-своему, кто-то их разгоняет, кто-то, возможно, наоборот ограничивает чтобы увеличить ресурс. Если скорость генерации +- с натяжкой еще билась, то расчетные цифры по контексту нейронка занижала вообще в разы. Мне просто стало интересно почему так: она говорит что максимальный контекст 20 тысяч условно, а я реально смог выжать 70 тысяч ( при тестировании контекст полностью не забивал, но как минимум программа запустилась и шла генерация, потому что бывало и такое что программа запустилась, но как только шла генерация сразу out of memry).
В общем я попросил сделать калькулятор по моим цифрам и по этой модели цифры бились, но когда я взял другую модель, вбил ее данные - цифры снова стали не актуальными. Тогда я решил прогнать на своем компе несколько моделей, замерил скорость, замерил +- максимально возможный контекст, при котором llama.cpp не вылетает по нехватке памяти, добавил метаданные с карточек моделей на huggingface и попросил нейронку сделать калькулятор, чтобы он показывал цифры близкие мне. Вот таблица моих замеров:
Скрытый текст
модель |
токенов |
время, с |
т/с |
квант |
контекст |
вес модели |
Qwen_Qwen3.5-4B-Q4_K_M.gguf |
5079 |
57 |
89,11 |
q4 |
26000 |
3,0 ГБ (3 013 027 808 байт) |
Qwen_Qwen3.5-4B-Q4_K_M.gguf |
6864 |
77 |
89,14 |
q4 |
100000 |
3,0 ГБ (3 013 027 808 байт) |
Qwen_Qwen3.5-4B-Q4_K_M.gguf |
5271 |
55 |
95,84 |
q4 |
115000 |
3,0 ГБ (3 013 027 808 байт) |
Qwen_Qwen3.5-4B-Q8_0.gguf |
6096 |
88 |
69,27 |
q8 |
70000 |
4,6 ГБ (4 622 131 168 байт) |
Qwen_Qwen3.5-9B-Q4_K_M.gguf |
5570 |
100 |
55,7 |
q4 |
43000 |
6,2 ГБ (6 169 341 984 байта) |
модель |
токенов |
время, с |
т/с |
квант |
контекст |
вес модели |
Nanbeige_Nanbeige4.2-3B-Q4_K_M.gguf |
13860 |
230 |
60,26 |
q4 |
26000 |
2,7 ГБ (2 684 023 968 байт) |
Nanbeige_Nanbeige4.2-3B-Q8_0.gguf |
16031 |
384 |
41,75 |
q8 |
18000 |
4,4 ГБ (4 434 787 488 байт) |
После того как нейронка проанализировала данные из таблицы, она выяснила что эффективность видеокарты имела строго определенную задержку по времени на 1 токен (у меня оказалось 5мс), модель назвала его оверхед и внедрила в формулу скорости, итоговая страница калькулятора лежит вот тут: llamacpp Inference Speed Calculator. Интересно другое. Еще давно я обратил внимание на модель от Nanbeige - Nanbeige4.2-3B всего 3 млрд параметров, но "мои тесты" проходила довольно неплохо для 3 мрд параметров, но меня в этот раз смутило то что скорость генерации была меньше ожидаемой, а контекста хватает вообще впритирку. Выяснилось что модель дважды прогоняет по всем весам, и грубо говоря становится вдвойне больше. Этот нюанс оказалось что математически тоже легко считается, просто нейронка добавила поле num_loops, которое тоже присутствует в метаданных модели, и таким образом учитывает в финальной формуле.
Но вбивать параметры модели в калькулятор мне показалось как-то не круто, поэтому и решил сделать букмарклет - скрипт сам берет нужные метаданные (через /api/models/.../tree находим первый .gguf в репо, качаем первые 400 КБ (Range-запрос, не весь файл), парсим заголовок GGUF: block_count, head_count_kv, key_length, context_length, full_attention_interval (гибриды → KV-слоёв = слоёв/interval), num_loops (Nanbeige → ×2). Если не вышло (gated-репо, сеть) — покажет только t/s, а в консоли (F12) будет пустой {}. ), подставляет их в формулу и рисует нужные цифры. Важно только в начале подставить свои цифры в константы.
Скрытый текст
Константы (в начале, правь под себя)
Значение |
Что это |
|
|---|---|---|
|
448 |
ПСП памяти, ГБ/с |
|
5 |
overhead, мс/токен |
|
8 |
VRAM, ГБ |
|
1.2 |
резерв (из твоих точек OOM) |
|
2 |
байт/значение KV: 2 = F16 (flash attention), 4 = F32 |
Здесь резерв - это сколько видеопамяти занимает система, условные цифры которые.
Код букмерклета - просто добавить в закладки:
javascript:(async()=>{const BW=448,OV=5,V=8,R=1.2,KB=2,E=document.getElementsByClassName('group/gguf');if(!E.length)return alert('Нет GGUF-квантов');let u;try{const p=location.pathname.split('/'),j=await(await fetch('/api/models/'+p[1]+'/'+p[2]+'/tree/main?recursive=true%27)).json(),f=j.find(x=>/\.gguf$/i.test(x.path||%27%27));if(f)u=%27/%27+p[1]+%27/%27+p[2]+%27/resolve/main/%27+encodeURI(f.path)}catch(e){}const M={};if(u)try{const r=await fetch(u,{headers:{Range:%27bytes=0-400000%27}});if(r.status!=206)throw 0;const b=await r.arrayBuffer(),d=new DataView(b),t=new TextDecoder(),G=a=>Number(d.getBigUint64(a,1)),U=a=>d.getUint32(a,1);if(U(0)!=0x46554747)throw 0;let o=24;const n=G(16),rs=()=>{const l=G(o);o+=8;const s=t.decode(new Uint8Array(b,o,l));o+=l;return s},rv=y=>{if(y==8)return rs();if(y==9){const e=U(o);o+=4;const c=G(o);o+=8;for(let i=0;i<c;i++)rv(e);return}const z=[1,1,2,2,4,4,4,1,0,0,8,8,8][y],p=o;o+=z;return y==0?b[p]:y==2?d.getUint16(p,1):y==4?U(p):y>9?G(p):0};for(let i=0;i<n;i++){const k=rs();if(k[0]==%27t%27&&M.block_count)break;const y=U(o);o+=4;const v=rv(y),s=k.split(%27.%27).pop();if(k.indexOf(%27linear%27)<0&&/^(block_count|head_count(_kv)?|key_length|embedding_length|context_length|full_attention_interval|num_loops)$/.test(s))M[s]=v}}catch(e){}const I=M.full_attention_interval,H=M.head_count_kv||M.head_count,D=M.key_length||(M.embedding_length&&M.head_count?M.embedding_length/M.head_count:0),P=M.num_loops||1,K=M.block_count&&H&&D?2*(I?Math.ceil(M.block_count/I):M.block_count)*H*D*KB:0;for(const el of E){if(el.dataset.sc)continue;el.innerHTML=el.innerHTML.replace(/[\d.]+\s*GB/g,m=>{const z=parseFloat(m),q=1000/(P*z*1000/BW+OV);let c=%27%27;if(K){let x=Math.floor((V-z-R)*2**30/K);if(M.context_length)x=Math.min(x,M.context_length);c=%27 | %27+(x>0?Math.round(x/1000)+%27k%27:%27OOM%27)}return m+%27 <b style="color:lime">(%27+Math.round(q)+%27 t/s%27+c+(z>V?%27 >VRAM%27:%27%27)+%27)</b>%27});el.dataset.sc=1}console.log(M)})()
Этот букмарклет конечно же больше баловство, потому что не учитывает много нюансов - те же MOE модели он не предназначен считать, метаданные считывает не всегда корректно, скорее всего не все учитывает, мало того, тот же контекст можно запускать с квантованием и тогда цифры будут совсем другие, да и скорость то же самое. Вот один пользователь Хабра смог ускорить генерацию просто поиграв с флагами запуска с 39 до 75 ток/с, рекомендую. И кстати, поэтому я и решил написать статью, может быть можно сделать калькулятор который сможет рассчитывать влияние флагов на скорость и контекст? То есть вдруг кого-то эта идея сподвигнет написать нормальный букмарклет (или калькулятор) который сможет точнее предсказывать скорость генерации и размер контекста под пользовательское железо. Делитесь в комментах правильно ли считает этот калькулятор для вашего железа, такие же цифры по "эффективности" (оверхед 5 мс) или может я вообще просто подогнал результат по 3 модели и на другие не работает (скорее всего так и есть).