用途
本地用 llama.cpp 启动一个带识图能力的模型服务,配套两个文件放在同一目录:start.bat(双击入口)+ start_server.ps1(实际逻辑)。
1 使用方法
- 把
start.bat、start_server.ps1、llama-server.exe、models文件夹放在同一目录下 models文件夹里放好主模型(文本)和视觉模型(mmproj-开头的文件)- 双击
start.bat即可,不用手动改 PowerShell 执行策略 - 启动成功后访问
http://127.0.0.1:8080
2 脚本原理
- 自动读取
nvidia-smi显存信息(总量/已用/空闲) - 自动扫描
models目录:文件名以mmproj开头的当视觉模型,其余当主模型 - 按剩余显存自动挑选能装下的最大主模型(多个量化版本时优先选质量最好的)
- 根据是否完全装得下显存,自动决定
-ngl(GPU 卸载层数):装得下就全部塞 GPU(99),装不下就按比例部分卸载,保证至少能跑起来
3 踩过的坑(重要)
.ps1文件必须保存为 UTF-8 with BOM,否则 Windows PowerShell 5.1 会用系统默认代码页(GBK)解析文件,中文注释会把字符串/引号解析错位,导致一堆莫名其妙的语法报错(缺少表达式、字符串缺少终止符等).bat文件反过来必须用纯英文或 ANSI(GBK) 编码,UTF-8 编码的.bat会导致中文行被拆散成乱码命令- 单 GPU 时
nvidia-smi只输出一行,PowerShell 会把这行当作纯字符串而不是数组,直接用$gpuLines[0]取到的是第一个字符而不是整行,必须用@()强制转数组:$gpuLines = @($gpuLines) $args是 PowerShell 内建自动变量,脚本里存启动参数不要用这个名字,改用$serverArgs
4 start.bat
@echo off
title Smart Launcher v3
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0start_server.ps1"
echo.
echo ====================================
echo Done. Press any key to close.
echo ====================================
pause >nul5 start_server.ps1
# ==============================
# Auto GPU Safe Mode
# Smart Launcher v5 (auto scan + auto select text/vision model)
# ==============================
cd $PSScriptRoot
Write-Host "===================================="
Write-Host " Smart Launcher v5"
Write-Host "===================================="
if (-not (Test-Path "models")) {
Write-Host "未找到 models 目录,请确认脚本放置位置正确" -ForegroundColor Red
exit 1
}
# 1. 获取显存信息(只取第一块 GPU,避免多卡时 -split 出错)
try {
$gpuLines = nvidia-smi --query-gpu=memory.total,memory.used --format=csv,noheader,nounits
if (-not $gpuLines) {
throw "nvidia-smi 未返回数据"
}
# 强制转换为数组:只有一块 GPU 时 nvidia-smi 只输出一行,
# PowerShell 会把它当作纯字符串而不是数组,此时 [0] 取到的是第一个字符而非整行,
# 用 @() 包一层强制变成数组,保证单卡/多卡场景下取的都是第一行完整内容
$gpuLines = @($gpuLines)
$gpuLine = $gpuLines[0]
$total, $used = $gpuLine -split ",\s*"
$total = [int]$total
$used = [int]$used
$free = $total - $used
} catch {
Write-Host "无法获取 GPU 信息,请确认已安装 NVIDIA 驱动且 nvidia-smi 可用" -ForegroundColor Red
Write-Host "错误详情: $_" -ForegroundColor Red
exit 1
}
Write-Host "GPU Total: $total MB"
Write-Host "GPU Used : $used MB"
Write-Host "GPU Free : $free MB"
# 2. 扫描 models 目录,自动区分「主模型(文本)」与「视觉模型(mmproj)」
$allGguf = Get-ChildItem models\*.gguf -ErrorAction SilentlyContinue
if (-not $allGguf) {
Write-Host "models 目录中没有找到任何 .gguf 文件" -ForegroundColor Red
exit 1
}
$mmprojFiles = $allGguf | Where-Object { $_.Name -match "^mmproj" }
$textModels = $allGguf | Where-Object { $_.Name -notmatch "^mmproj" }
if (-not $textModels) {
Write-Host "models 目录中没有找到主模型文件(排除 mmproj 后为空)" -ForegroundColor Red
exit 1
}
if (-not $mmprojFiles) {
Write-Host "models 目录中没有找到 mmproj 视觉模型文件,无法启用识图" -ForegroundColor Red
exit 1
}
$mmproj = $mmprojFiles | Sort-Object Name | Select-Object -First 1
$mmprojSizeMB = [math]::Round($mmproj.Length / 1MB)
Write-Host "扫描到 $($textModels.Count) 个主模型候选,$($mmprojFiles.Count) 个视觉模型候选(重复项已去重,取第一个)"
Write-Host "Vision Model: $($mmproj.Name) (约 $mmprojSizeMB MB)"
# 3. 根据剩余显存自动选择合适大小的主模型
$ctx = 4096
$contextOverheadMB = 800
$safetyMarginMB = 500
$reserveMB = $mmprojSizeMB + $contextOverheadMB + $safetyMarginMB
$budgetMB = $free - $reserveMB
Write-Host "预留显存(视觉模型 + 上下文 + 安全余量): $reserveMB MB"
Write-Host "可用于主模型的显存预算: $budgetMB MB"
$sortedModels = $textModels | Sort-Object Length -Descending
$chosenModel = $null
$fullyFits = $false
foreach ($m in $sortedModels) {
$sizeMB = [math]::Round($m.Length / 1MB)
if ($sizeMB -le $budgetMB) {
$chosenModel = $m
$fullyFits = $true
break
}
}
if (-not $chosenModel) {
$chosenModel = $sortedModels | Sort-Object Length | Select-Object -First 1
$fullyFits = $false
Write-Host "警告: 显存不足以完整装下任何模型,将选择最小的模型并降低 GPU 卸载层数(推理速度会变慢)" -ForegroundColor Yellow
}
$modelSizeMB = [math]::Round($chosenModel.Length / 1MB)
Write-Host "Selected Model: $($chosenModel.Name) (约 $modelSizeMB MB)"
# 4. 决定 -ngl(GPU 卸载层数)
if ($fullyFits) {
$ngl = 99
} else {
$ratio = $budgetMB / $modelSizeMB
if ($ratio -lt 0) { $ratio = 0 }
$ngl = [math]::Max(1, [math]::Floor(40 * $ratio))
Write-Host "部分卸载模式,估算 NGL: $ngl(如仍报显存不足,可手动调低此值,或删除更大的模型只保留小量化版本)" -ForegroundColor Yellow
}
Write-Host "Selected NGL: $ngl"
Write-Host "CTX: $ctx"
# 5. 组装启动参数
$modelPath = $chosenModel.FullName
$mmprojPath = $mmproj.FullName
$serverArgs = @(
"-m", $modelPath,
"--mmproj", $mmprojPath,
"-ngl", "$ngl",
"-c", "$ctx",
"--host", "127.0.0.1",
"--port", "8080",
"--cont-batching"
)
# 6. 检查可执行文件是否存在
$exePath = ".\llama-server.exe"
if (-not (Test-Path $exePath)) {
Write-Host "未找到 $exePath,请确认脚本与可执行文件在同一目录" -ForegroundColor Red
exit 1
}
# 7. 启动服务
Write-Host "Starting server..."
Write-Host "Command: $exePath $($serverArgs -join ' ')"
Start-Process -FilePath $exePath -ArgumentList $serverArgs -NoNewWindow -Wait