TL;DR
작성자는 RX 6650 XT와 llama.cpp Vulkan 환경에서 speculative decoding의 전력 효율을 측정해 단일 스트림에서 토큰당 에너지가 1.42J에서 1.56J로 약 10% 증가했고 8스트림에서는 0.47J에서 1.03J로 121% 증가하는 등 추측 검증 오버헤드가 GPU 부하가 높을수록 크게 악화된다는 결과를 보고했다. 전력은 LibreHardwareMonitor로 약 6Hz 샘플링해 각 생성 구간을 적분해 계산했으며 실험은 동일 프롬프트와 반복 측정, 드래프트 튜닝(--spec-draft-n-max 6 --spec-draft-p-min 0.75)을 통해 수용률을 92–94%로 개선한 상태에서도 타깃 단순 디코딩을 이기지 못했다. 반면 배치(동시성 증대)는 토큰당 에너지를 약 3배 낮추는 큰 이득을 보여 배치 최적화가 우선적인 비용·에너지 절감 수단임이 확인되었고 모델 크기 비율과 하드웨어·백엔드 의존성이 추측적 디코딩 효과를 결정한다고 결론지었다. 이 결과는 AMD/Vulkan 및 단일 GPU 환경에 대한 사례이며 NVIDIA/CUDA나 다른 모델 조합에서는 다른 결과가 나올 수 있어 추가 재현이 필요하다.
실용적 조언
- 실험자는 먼저 GPU 패키지 전력만을 측정할 때에도 실제 소비 에너지(J)를 얻기 위해 전력 샘플을 고정 간격으로 폴링해 구간을 적분해야 한다고 권고했다. 전력 샘플링 주파수는 약 150ms 내외로 설정해 각 추론 구간과 정확히 매칭해야 하며 로그와 조건 윈도우를 동기화하면 J/token 계산이 가능하다. 이러한 절차는 추측적 디코딩의 에너지 영향 평가에서 필수적이다.
- 추측적 디코딩을 실무에 적용하려면 드래프트 수용률, 드래프트/타깃 모델 크기 비율, 시스템 부하(동시성) 세 축을 먼저 평가해야 한다고 실험 결과가 시사한다. 수용률 튜닝이 가능하더라도 타깃 대비 드래프트의 상대적 실행 비용이 크면 에너지 이득이 사라질 수 있으므로 작은 모델 비율을 확보하거나 배치 최적화를 우선 적용하는 편이 효과적이다. 하드웨어·백엔드에 따라 행동이 달라지므로 NVIDIA/CUDA 환경에서의 재검증을 권장한다.
섹션별 상세
param([string]$OutFile = "D:\\ai-energy-test\\power.csv")
[System.Reflection.Assembly]::LoadFrom("D:\\ai-energy-test\\lhm\\LibreHardwareMonitorLib.dll") | Out-Null
$pc = New-Object [LibreHardwareMonitor.Hardware.Computer]
$pc.IsGpuEnabled = $true
$pc.Open()
$gpu = $pc.Hardware | Where-Object { $_.HardwareType -eq 'GpuAmd' } | Select-Object -First 1
$sw = New-Object System.IO.StreamWriter($OutFile, $false)
$sw.AutoFlush = $true
$sw.WriteLine("ts,power,clock")
while ($true) {
$gpu.Update()
$p = ($gpu.Sensors | Where-Object { $_.SensorType -eq 'Power' -and $_.Name -eq 'GPU Package' } | Select-Object -First 1).Value
$c = ($gpu.Sensors | Where-Object { $_.SensorType -eq 'Clock' -and $_.Name -eq 'GPU Core' } | Select-Object -First 1).Value
$ts = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
$sw.WriteLine("$ts,$p,$c")
Start-Sleep -Milliseconds 150
}이 스크립트는 LibreHardwareMonitor 라이브러리를 로드해 GPU 패키지 전력을 약 150ms 간격으로 폴링해 타임스탬프와 함께 CSV로 기록하는 전력 로거 예시이다. 전력값과 GPU 코어 클럭을 기록하므로 추론 구간의 평균 전력과 총 에너지(J)를 계산할 수 있다. 실험 재현 시 전력 샘플링과 타임라인 동기화에 사용된다.
param(
[int]$Port = 8090,
[int]$Conc = 1,
[int]$Waves = 4,
[int]$NPredict = 256,
[string]$Label = "cond",
[string]$OutDir = "D:\\ai-energy-test\\results"
)
$ErrorActionPreference = 'Stop'
New-Item -ItemType Directory -Force $OutDir | Out-Null
$prompts = @(
"Write a detailed explanation of how photosynthesis works, aimed at a curious teenager.",
"Write a Python function that parses a CSV file and returns a dictionary of column statistics (mean, min, max). Include docstrings.",
"Explain the causes of the First World War in a structured essay with clear paragraphs.",
"Write a JavaScript function that debounces another function, with comments explaining each step.",
"Describe a typical day in the life of a lighthouse keeper in the 1890s, in vivid detail.",
"Write a SQL schema for a small library management system with books, members, and loans. Explain each table.",
"Explain how vaccines train the immune system, using simple analogies.",
"Write a Python class implementing a simple LRU cache with get and put methods, with comments."
)
$tmp = Join-Path $OutDir "tmp_$Label"
New-Item -ItemType Directory -Force $tmp | Out-Null
$startMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
$allOut = @()
for ($w = 0; $w -lt $Waves; $w++) {
$procs = @()
for ($i = 0; $i -lt $Conc; $i++) {
$p = $prompts[(($w * $Conc) + $i) % $prompts.Count]
$body = @{ prompt = $p; n_predict = $NPredict; temperature = 0; cache_prompt = $false } | ConvertTo-Json
$bodyFile = Join-Path $tmp "body_${w}_$i.json"
$outFile = Join-Path $tmp "out_${w}_$i.json"
[System.IO.File]::WriteAllText($bodyFile, $body, (New-Object System.Text.UTF8Encoding($false)))
$procs += Start-Process -FilePath curl.exe -ArgumentList @('-s','-X','POST',"http://127.0.0.1:$Port/completion",'-H','Content-Type: application/json','-d',"@$bodyFile",'-o',$outFile) -NoNewWindow -PassThru
$allOut += $outFile
}
$procs | Wait-Process
}
$endMs = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
$tokens = 0; $draftN = 0; $draftAcc = 0; $reqs = 0
foreach ($f in $allOut) {
$j = Get-Content $f -Raw | ConvertFrom-Json
$tokens += [int]$j.timings.predicted_n
$reqs++
if ($null -ne $j.timings.draft_n) { $draftN += [int]$j.timings.draft_n; $draftAcc += [int]$j.timings.draft_n_accepted }
}
$rec = [pscustomobject]@{
label = $Label; start_ms = $startMs; end_ms = $endMs
conc = $Conc; waves = $Waves; requests = $reqs
tokens = $tokens; draft_n = $draftN; draft_accepted = $draftAcc
}
$rec | ConvertTo-Json -Compress | Add-Content (Join-Path $OutDir "conditions.jsonl") -Encoding ascii
$rec | Format-List이 스크립트는 여러 동시 요청을 llama-server에 보내 각 실험 조건의 시작·종료 시간과 예측 토큰 수·드래프트 통계를 기록해 전력 로그와 연동 가능한 조건 메타데이터를 생성한다. 동시성(Conc)와 파형(Waves)을 조절해 1스트림·8스트림 실험을 자동화하며 결과를 JSONL로 저장한다. 전력-작업 매칭과 J/token 계산을 위해 반드시 함께 실행해야 하는 스크립트이다.
$power = Import-Csv 'D:\\ai-energy-test\\power.csv' | ForEach-Object { [pscustomobject]@{ ts=[long]$_.ts; p=[double]$_.power } }
$conds = Get-Content 'D:\\ai-energy-test\\results\\conditions.jsonl' | ForEach-Object { $_ | ConvertFrom-Json }
$rows = foreach ($cd in $conds) {
$w = $power | Where-Object { $_.ts -ge $cd.start_ms -and $_.ts -le $cd.end_ms }
if ($w.Count -lt 2) { continue }
$meanP = ($w.p | Measure-Object -Average).Average
$durS = ($cd.end_ms - $cd.start_ms)/1000.0
$acc = $null
if ($cd.draft_n -gt 0) { $acc = [math]::Round(100.0*$cd.draft_accepted/$cd.draft_n,1) }
[pscustomobject]@{
label=$cd.label; tokens=$cd.tokens
dur_s=[math]::Round($durS,1)
tok_s=[math]::Round($cd.tokens/$durS,1)
mean_W=[math]::Round($meanP,1)
J_per_tok=[math]::Round(($meanP*$durS)/$cd.tokens,3)
accept=$acc
}
}
$rows | Format-Table -AutoSize이 스크립트는 전력 CSV와 조건 JSONL을 합쳐 각 조건의 평균 전력, 초당 토큰, 토큰당 줄(J/token) 등을 계산해 요약 테이블을 만든다. 전력 샘플 타임스탬프와 조건 윈도우를 매칭해 실제 소비 에너지를 구하는 핵심 후처리 로직을 포함한다. 실험 결과의 통계적 평균과 토큰당 에너지 수치를 산출하는 마지막 단계이다.
llama-server -m qwen2.5-3b-instruct-q4_k_m.gguf -md qwen2.5-0.5b-instruct-q4_k_m.gguf \
--spec-type draft-simple -ngl 99 -ngld 99 --spec-draft-n-max 6 --spec-draft-n-min 0 --spec-draft-p-min 0.75 \
-c 8192 -np 8 --port 8090이 명령은 llama-server를 타깃 모델과 드래프트 모델을 함께 로드해 추측적 디코딩을 활성화하고 드래프트 정책을 튜닝한 서버 실행 예시이다. 핵심 플래그는 드래프트 타입, 드래프트 최대 토큰, 최소 수용 확률(spec-draft-p-min) 등으로 수용률과 성능 균형을 조절한다. 재현을 위해 동일한 llama.cpp 빌드와 옵션 사용이 요구된다.
용어 해설
- 추측적 디코딩(Speculative Decoding)
- — 추측적 디코딩은 빠른 저용량 'draft' 모델로 미래 토큰을 미리 생성하고 이후 고성능 'target' 모델에서 검증하거나 대체해 전체 추론 비용을 줄이는 기법이다. 구체적으로는 draft가 제안한 토큰을 target이 검증하는 과정에서 성공 비율(acceptance rate)이 높을수록 이득이 발생하며 드래프트의 속도·정확도·모델 크기 비율이 핵심 변수이다. 데이터센터·엣지 환경에서 추론 지연과 에너지 소비를 절감하기 위해 사용된다.
- 드래프트 모델(Draft Model)
- — 드래프트 모델은 추측적 디코딩에서 먼저 토큰을 생성하는 저비용·저지연 모델로서 제안 토큰을 만든 뒤 타깃 모델이 이를 검증하거나 이어서 생성하는 방식으로 동작한다. 드래프트의 추론 속도와 모델 크기가 작을수록 검증 오버헤드 대비 유리하지만 드래프트의 낮은 품질이 오히려 재검증 비용을 유발할 수 있다. 본 게시물에서는 Qwen2.5-0.5B가 드래프트로 사용되었다.
- 수용률(Acceptance Rate)
- — 수용률은 드래프트가 제안한 토큰을 타깃 모델이 그대로 받아들이는 비율로, 추측적 디코딩의 유효성을 직접 결정하는 지표이다. 높은 수용률은 드래프트가 제안한 토큰을 재계산 없이 사용할 수 있음을 뜻해 전체 연산과 에너지 절감을 가능하게 한다. 게시물에서는 기본 설정에서 약 15%였던 수용률이 튜닝 후 92–94%로 상승했지만 전력 이득은 발생하지 않았다.
- 배치 처리(Batching)
- — 배치 처리는 여러 요청을 묶어 한 번에 처리함으로써 GPU의 고정 전력과 메모리 접근 비용을 여러 토큰에 분산시키는 최적화 기법이다. 본 실험에서는 단일 스트림에서 다중 동시 스트림(8스트림)으로 바꾼 결과 토큰당 에너지가 약 3배 줄어드는 이득이 관찰되었다. 실무에서는 긴 대화나 높은 동시성 환경에서 비용·지연 최적화 수단으로 활용된다.
언급된 도구
경량화된 로컬 추론을 위한 라이브러리와 서버 런타임을 제공해 Vulkan 빌드에서 모델을 로드하고 추론을 수행하는 데 사용되었다.
GPU 패키지 전력 센서를 폴링해 전력 로그를 CSV로 기록하는 데 사용된 도구로서 전력 적분을 통해 J/token 산출에 쓰였다.
llama-server에 HTTP 요청을 보내 동시 요청 및 응답 타이밍을 수집하는 데 사용된 범용 클라이언트이다.
AI 요약 · 북마크 · 개인 피드 설정 — 무료
출처 · 인용 안내
인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.