Under de senaste veckorna har jag sett mycket entusiasm i r/LocalLLaMA-communityn kring spekulativ avkodning, DFlash, bättre hantering av KV-cache och optimerade byggnader av llama.cpp. Det intressanta är att folk får stora hastighetsökningar utan att uppgradera sin hårdvara.
I den här guiden kör vi Gemma 4 31B IT lokalt på en RTX 4090 24GB med BeeLlama.cpp, en fork av llama.cpp som stödjer DFlash spekulativ avkodning.
Vi kommer att testa modellen på två sätt. Först kör vi den utan DFlash för att skapa en baslinje. Därefter kör vi den med DFlash för att jämföra hastighetsförbättringen.
Vad är DFlash?
Enkelt uttryckt använder DFlash en utkastmodell för att förutsäga flera token i förväg, medan huvudmodellen verifierar dessa token i stället för att generera allt en token i taget. När många utkasttoken godkänns blir genereringen mycket snabbare samtidigt som utdata ligger nära originalmodellen.
I mitt experiment gav DFlash nästan en 3,7x hastighetsökning i vissa uppgifter, med utdata som var mycket lik baslinjen. Målet med den här guiden är att visa upplägget, köra båda versionerna och jämföra resultaten tydligt.
Hur DFlash fungerar
Standardgenerering i LLM:er är långsam eftersom de flesta modeller genererar text en token i taget. Varje token beror på den föregående, så modellen måste ta sig steg för steg genom svaret.
DFlash snabbar upp det med spekulativ avkodning.
I stället för att låta huvudmodellen generera varje token direkt använder DFlash en separat utkastmodell för att gissa flera kommande token först. Huvudmodellen verifierar sedan dessa utkasttoken i ett större steg. Om utkasttokenen är bra accepterar huvudmodellen dem. Om någon av dem är felaktig korrigerar huvudmodellen den och fortsätter.
Ett enkelt sätt att tänka på det:
- Utan DFlash: huvudmodellen skriver en token i taget.
- Med DFlash: utkastmodellen föreslår ett block av token, och huvudmodellen kontrollerar snabbt vilka som kan accepteras.

Diagram över arbetsflödet för DFlash spekulativ avkodning.
Detta är särskilt användbart för strukturerade uppgifter som programmering. Kod följer ofta förutsägbara mönster såsom imports, funktionsdefinitioner, indentering, loopar och vanlig syntax. Tack vare detta kan utkastmodellen ofta gissa nästa token korrekt, vilket gör att huvudmodellen kan acceptera fler token i varje steg.
DFlash vs MTP: Vad är skillnaden?
DFlash och Multi-Token Prediction (MTP) syftar båda till att lösa samma problem: de hjälper modellen att generera mer än en token per dyrt avkodningssteg.
Skillnaden är hur de skapar utkasttoken.
|
Metod |
Hur det fungerar |
Behövs extra modell? |
Styrka |
|
MTP |
Använder inbyggda multi-token-prediktionshuvuden för att förutsäga framtida token |
Vanligtvis ingen separat utkastmodell |
Enklare setup när modellen redan stöder MTP |
|
DFlash |
Använder en separat DFlash-utkastmodell för att föreslå större block av token |
Ja |
Kan ge starka hastighetsökningar på strukturerade utdata som kod |
Enkelt uttryckt är MTP vanligen inbyggt i själva modellen. Det förutspår flera framtida token med interna prediktionshuvuden, så när det stöds kan det vara enklare att konfigurera och mer minneseffektivt.
DFlash använder däremot en separat utkastmodell. Det kan göra uppsättningen något tyngre, men tillåter också mer aggressiv utkastning. Därför kan DFlash ge stora hastighetsökningar i strukturerade uppgifter där nästa token är lättare att förutsäga.
1. Konfigurera miljön
Jag rekommenderar starkt att du kör detta lokalt om du har ett RTX 3090- eller RTX 4090-GPU. Annars kan du hyra ett GPU från RunPod, Vast.ai eller någon annan GPU-leverantör.
I den här guiden använder vi en RunPod RTX 4090-pod. Jag började med den senaste RunPod PyTorch-mallen och gjorde några små ändringar:
- Exponerade port 8910 för llama.cpp-servern
- Ökade beständig lagring till 100 GB
- Lade till min Hugging Face-token för att snabba upp modellnedladdning
Med detta upplägg kostar poden omkring $0,70 per timme, beroende på aktuell RunPod-prissättning och tillgänglighet.

När poden är distribuerad, öppna JupyterLab från RunPods instrumentpanel. Starta sedan en ny terminal och installera grundläggande beroenden:
apt update
apt install -y git cmake build-essential curl wget python3-pip

2. Klona BeeLlama.cpp
Nästa steg är att klona BeeLlama.cpp, den llama.cpp-fork vi ska använda i detta upplägg.
BeeLlama.cpp är utformat för snabbare lokal GGUF-inferens samtidigt som det bekanta arbetsflödet i llama.cpp behålls. Du får samma typ av verktyg, inklusive llama-server, men med extra prestandafokuserade funktioner som DFlash spekulativ avkodning, adaptiv utkastkontroll och TurboQuant/TCQ KV-cachekomprimering.
Kör följande kommandon i din JupyterLab-terminal:
git clone https://github.com/Anbeeld/beellama.cpp.git
cd beellama.cpp
Detta hämtar BeeLlama.cpp-förrådet och flyttar dig till projektmappen. Alla byggkommandon i nästa steg ska köras inifrån denna katalog.
3. Bygg BeeLlama.cpp med CUDA
Nu ska vi bygga BeeLlama.cpp med CUDA-stöd så att den kan använda RTX 4090 på rätt sätt.
För detta upplägg aktiverar vi CUDA, Flash Attention, inbyggda CPU-optimeringar och kvantiserade Flash Attention-kärnor. Eftersom vi använder en RTX 4090 sätter vi också CUDA-arkitekturen till 89.
cmake -B build -DGGML_CUDA=ON -DGGML_NATIVE=ON \
-DGGML_CUDA_FA=ON -DGGML_CUDA_FA_ALL_QUANTS=ON \
-DCMAKE_CUDA_ARCHITECTURES=89 \
-DCMAKE_BUILD_TYPE=Release
cmake --build build -j
Bygget kan ta 20 minuter. Under kompileringen kan du se varningar relaterade till TurboQuant, TCQ eller DFlash CUDA-deklarationer. I mitt fall var detta bara varningar och stoppade inte bygget.

Avsluta med att kopiera serverbinären till huvudprojektmappen så att den blir enklare att köra senare:
cp ./build/bin/llama-server ./llama-server
4. Installera Hugging Face CLI och ladda ner modellerna
Nu behöver vi ladda ner två GGUF-filer: huvudmodellen och DFlash-utkastmodellen.
Huvudmodellen är den som producerar slutlig utdata. DFlash-utkastmodellen är mycket mindre och används bara för att förutsäga token före huvudmodellen. Huvudmodellen verifierar fortfarande de genererade tokenen, så utkastmodellen finns där för att snabba upp avkodningen snarare än att ersätta huvudmodellen.
Börja med att installera Hugging Face CLI:
pip install -U huggingface_hub
Skapa sedan en mapp för att hålla modelfilerna organiserade:
mkdir -p models
Ladda ner huvudmodellen Gemma 4 31B IT GGUF:
hf download unsloth/gemma-4-31B-it-GGUF \
gemma-4-31B-it-Q4_K_S.gguf \
--local-dir models
Ladda sedan ner DFlash-utkastmodellen:
hf download Anbeeld/gemma-4-31B-it-DFlash-GGUF \
gemma4-31b-it-dflash-Q5_K_M.gguf \
--local-dir models
DFlash-utkastmodellen listas på Hugging Face som en dflash-draft-arkitekturmodell, där Q5_K_M-filen är runt 1,09 GB, så den är mycket mindre än huvudmodellen på 31B. Det gör den praktisk att ladda tillsammans med huvudmodellen för spekulativ avkodning.
5. Kör Gemma 4 31B utan DFlash
Innan vi aktiverar DFlash behöver vi först köra Gemma 4 31B normalt. Det ger oss en baslinje för genereringshastighet, VRAM-användning och utdatakvalitet. Senare jämför vi denna baslinje med DFlash-körningen för att se den faktiska hastighetsökningen.
Kör följande kommando inifrån beellama.cpp-mappen:
./llama-server \
-m "models/gemma-4-31B-it-Q4_K_S.gguf" \
--host 0.0.0.0 \
--port 8910 \
-np 1 \
-ngl all \
-b 2048 -ub 512 \
--ctx-size 32768 \
--cache-type-k q5_0 \
--cache-type-v q4_1 \
--flash-attn on \
--jinja \
--metrics \
--log-timestamps \
--log-prefix \
--reasoning off \
--temp 0.7 \
--top-k 64 \
--top-p 0.95 \
--min-p 0.0
Detta kommando startar modellservern på port 8910. Eftersom vi exponerade port 8910 när vi skapade RunPod-poden kan vi komma åt modellen direkt från webbläsaren.
När modellen har laddats in i GPU-minnet bör du se ett meddelande som visar att servern körs på: 0.0.0.0:8910.

Gå nu tillbaka till RunPods instrumentpanel och klicka på portlänken som är kopplad till 8910.

Detta öppnar webbgränssnittet för llama.cpp, där du kan testa modellen i ett enkelt chattliknande UI.

Vid det här laget kan du prova att ställa några längre eller mer komplexa frågor så att du kan observera genomsnittlig tokenhastighet. I min baslinjekörning utan DFlash fick jag omkring 41 token per sekund i snitt.

6. Utvärdera baslinjemodellen
Nu när baslinjemodellen körs behöver vi ett enkelt sätt att mäta dess genereringshastighet. För detta använder vi tre kodningsprompter och skickar dem till den lokala llama.cpp-servern via OpenAI-kompatibla endpointen för chattkompletteringar.
Målet är inte att skapa en perfekt benchmarksvit. Vi vill bara ha en konsekvent baslinje så att vi kan jämföra samma prompter senare med DFlash aktiverat.
Öppna en ny Jupyter Terminal-flik och skapa ett testskript:
cat > test_llm_prompts.sh <<'EOF'
#!/usr/bin/env bash
PORT="${1:-8910}"
MODEL="${2:-local-gemma}"
PREFIX="${3:-run}"
URL="http://localhost:${PORT}/v1/chat/completions"
PROMPTS=(
"Write a complete Python task store module. Include a Task dataclass, TaskStatus enum, TaskStore class, add_task, update_task, delete_task, search_tasks, filter_by_status, export_to_json, get_all_tasks, and 5 tests. Return only one complete Python file."
"Write a complete Python key-value report module. Include a KeyValueStore class, set, get, delete, exists, list_keys, filter_by_prefix, export_to_json, load_from_json, and a generate_report function that returns total keys, empty values, prefix counts, and largest value length. Include 5 tests. Return only one complete Python file."
"Write a complete Python doubly linked list module. Include a Node dataclass, DoublyLinkedList class, append, prepend, delete, find, reverse, to_list, from_list, clear, and 5 tests. Return only one complete Python file."
)
echo "Testing server: $URL"
echo "Model: $MODEL"
echo "Output prefix: $PREFIX"
for i in "${!PROMPTS[@]}"; do
NUM=$((i+1))
OUT="${PREFIX}_prompt_${NUM}.json"
echo ""
echo "Running prompt ${NUM}..."
echo "Saving to ${OUT}"
echo "--------------------------------"
jq -n \
--arg model "$MODEL" \
--arg prompt "${PROMPTS[$i]}" \
'{
model: $model,
messages: [
{
role: "user",
content: $prompt
}
],
max_tokens: 1200,
temperature: 0.7
}' | curl -s "$URL" \
-H "Content-Type: application/json" \
-d @- | tee "$OUT" | jq '.timings'
echo "Saved full result to ${OUT}"
done
echo ""
echo "Summary"
echo "--------------------------------"
for f in ${PREFIX}_prompt_*.json; do
echo "$f"
jq '{
model: .model,
prompt_tokens: .usage.prompt_tokens,
completion_tokens: .usage.completion_tokens,
total_tokens: .usage.total_tokens,
generation_speed_tok_s: .timings.predicted_per_second,
generation_time_sec: (.timings.predicted_ms / 1000),
draft_tokens: .timings.draft_n,
accepted_draft_tokens: .timings.draft_n_accepted
}' "$f"
done
EOF
På macOS eller Linux, kom ihåg att göra skriptet körbart:
chmod +x test_llm_prompts.sh
Kör det sedan mot baslinjemodellen:
./test_llm_prompts.sh 8910 local-gemma-baseline baseline
Detta skript skickar tre Python-kodgenereringsprompter till modellen och sparar varje fullständigt svar som en JSON-fil. Det skriver också ut användbar tidsinformation, inklusive completion-token, genereringshastighet, genereringstid och fält för utkasttoken.
Hela utskriften är ganska lång, så nedan följer en kort sammanfattning av baslinjeresultaten. Det ger en snabb överblick över hur modellen presterar innan vi aktiverar DFlash.
|
Prompt |
Completion-token |
Genereringshastighet |
Genereringstid |
|
Prompt 1: Task store-modul |
1124 |
40,66 tok/s |
27,64 s |
|
Prompt 2: Key-value report-modul |
1200 |
40,67 tok/s |
29,51 s |
|
Prompt 3: Doubly linked list-modul |
1200 |
40,72 tok/s |
29,47 s |
Över alla tre prompter höll sig baslinjemodellen mycket konsekvent runt 40,68 token per sekund. Detta ger oss en tydlig referenspunkt innan vi testar samma prompter med DFlash aktiverat.
7. Kör Gemma 4 31B med DFlash
Nu när vi har baslinjeresultaten kan vi köra samma modell igen med DFlash aktiverat.
Gå tillbaka till terminalen där baslinjeservern körs och stoppa den med Ctrl + C.
Starta sedan den optimerade DFlash-servern:
./llama-server \
-m "models/gemma-4-31B-it-Q4_K_S.gguf" \
--spec-draft-model "models/gemma4-31b-it-dflash-Q5_K_M.gguf" \
--spec-type dflash \
--spec-dflash-cross-ctx 1024 \
--host 0.0.0.0 \
--port 8910 \
-np 1 \
--kv-unified \
-ngl all \
--spec-draft-ngl all \
-b 2048 -ub 512 \
--ctx-size 32768 \
--flash-attn on \
--cache-ram 0 \
--jinja \
--no-mmap \
--mlock \
--no-host \
--metrics \
--log-timestamps \
--log-prefix \
--reasoning off \
--temp 0.7 \
--top-k 64 \
--top-p 0.95 \
--min-p 0.0
Detta kommando laddar samma huvudmodell Gemma 4 31B, men nu laddas också DFlash-utkastmodellen via --spec-draft-model.
De viktiga DFlash-relaterade flaggorna:
|
Flagga |
Syfte |
|
|
Laddar DFlash-utkastmodellen |
|
|
Aktiverar DFlash spekulativ avkodning |
|
|
Anger cross-context-fönster som används av DFlash |
|
|
Offloadar utkastmodellens lager till GPU:n |
|
|
Använder enhetlig KV-hantering för huvud- och utkastmodell |
Det kan ta lite längre tid att starta den här gången eftersom både huvudmodellen och DFlash-utkastmodellen måste laddas in i minnet.

När servern är helt laddad bör du återigen se inferensservern köra på: 0.0.0.0:8910.

8. Utvärdera DFlash-modellen
Gå nu tillbaka till Jupyter-terminalen där vi skapade benchmarkskriptet. Vi kan köra samma skript igen, men den här gången mot servern med DFlash aktiverat.
./test_llm_prompts.sh 8910 local-gemma-dflash dflash
Detta använder samma tre kodningsprompter från baslinjetestet, vilket gör jämförelsen rättvis. Den enda stora skillnaden är att servern nu körs med DFlash-utkastmodellen aktiverad.
Jämföra inferenshastighet
Hela utskriften är lång, så här är en kort sammanfattning av baslinje- och DFlash-resultat:
|
Prompt |
Baslinjehastighet |
DFlash-hastighet |
Hastighetsökning |
Baslinjetid |
DFlash-tid |
Tidsbesparing |
|
Task store-modul |
40,66 tok/s |
130,96 tok/s |
3,22x |
27,64 s |
8,23 s |
19,41 s |
|
Key-value report-modul |
40,67 tok/s |
145,68 tok/s |
3,58x |
29,51 s |
8,24 s |
21,27 s |
|
Doubly linked list-modul |
40,72 tok/s |
153,04 tok/s |
3,76x |
29,47 s |
7,84 s |
21,63 s |
Över dessa tre kodningsuppgifter ökade DFlash genereringshastigheten från cirka 40 tok/s till 130–153 tok/s. Det ger ungefär en 3,2x till 3,8x hastighetsökning, samtidigt som genereringstiden minskade från nästan 30 sekunder till runt 8 sekunder per prompt.
Du kan också öppna samma länk för port 8910 från RunPods instrumentpanel och testa modellen via webbgränssnittet.
Jämföra utdatakvalitet
Eftersom vi närmar oss en fyrfaldig hastighetsökning på kodningsprompter är nästa sak att kontrollera utdatakvaliteten. För det testade jag modellen på några olika uppgifter.
Först bad jag den att generera en enkel portföljsajt för ”Abid”. För en lokal 31B-modell som körs på en enda RTX 4090 var resultatet imponerande. Den producerade en ren struktur med användbar HTML och styling.

Därefter bad jag den att generera ett diagram för en komplett MLOps-pipeline. Modellen returnerade Mermaid-kod med etiketter, färger och ett komplett arbetsflöde. Jag testade koden, och den fungerade direkt.

Sedan bad jag den skriva ett blogginlägg om Mixture of Experts i LLM:er. Kvaliteten var fortfarande stark, men hastigheten sjönk till runt 95 tok/s. Det är fortfarande mycket snabbare än baslinjen, men långsammare än kodningsprompterna.

Detta är rimligt eftersom DFlash fungerar bäst när utdata är mer förutsägbara. Koduppgifter följer ofta tydliga mönster, så utkastmodellen kan gissa fler token korrekt. Kreativt skrivande eller forskningsliknande prompter är mindre förutsägbara, så modellen kan acceptera färre utkasttoken och hastighetsökningen kan bli lägre.
Avslutande tankar
Efter att ha testat detta upplägg tycker jag att spekulativ avkodning i kombination med bättre KV-cachehantering är den verkliga vinnaren för lokal LLM-inferens.
Den största fördelen är inte bara hastighetsökningen på papper. Det är vad den hastigheten möjliggör. När en 31B-modell kan generera kod i 130–150 token per sekund på en enda RTX 4090 börjar det kännas praktiskt som en lokal kodningsagent. Du kan använda den för att bygga projekt från grunden, koppla den till MCP-servrar, köra bash-verktyg, använda egna färdigheter och skapa ett arbetsflöde som känns mycket närmare premium-kodningsagenter.
För den som redan har en RTX 3090 eller 4090 är detta ännu mer spännande. I stället för att betala för varje kodassistent eller förlita sig helt på molnverktyg kan du köra en kraftfull lokal lösning som är snabb, privat och flexibel. Det ersätter kanske inte alla hostade verktyg för alla, men för lokala AI-entusiaster, utvecklare och byggare börjar det bli väldigt nära.
Jag tror också att detta bara är början. Många testar redan liknande upplägg med nyare modeller som Qwen3.6-27B och rapporterar ännu bättre kvalitet. I takt med att modellerna förbättras, utkastmodeller blir bättre och inferensmotorer som BeeLlama.cpp blir mer optimerade, kommer lokal AI bara att bli mer användbar.
Det bästa är communityn runt detta. Många av dessa förbättringar kommer från lokala AI-entusiaster som experimenterar, benchmarkar, förbättrar verktygen och delar sina resultat öppet. Det gör det enklare för resten av oss att replikera upplägget och uppleva samma prestandavinster.
