1.诊断:
#!/usr/bin/env bash
# ============================================================================
# collect-diag.sh — H100 现场一键离线诊断采集(全程只读,不改任何东西)
#
# 用途:部署失败时,把定位根因所需的全部信息一次性采全,写成一个 txt 带回。
#
# 用法(在目标机上,插好 U 盘后):
# sudo bash /mnt/usb/collect-diag.sh
# 报告会自动写到【脚本所在目录】下的 diag-report-时间.txt,
# 也就是直接落到 U 盘里,拔下来带回发给工程师即可。
#
# 安全性:全程只跑 nvidia-smi / lspci / dmesg / docker inspect|logs / cat 这类
# 查看命令,绝不 up / down / restart / 删改任何文件或服务。可放心多跑几次。
#
# 若运行时报 “$'\r': command not found” 或 “bad interpreter”,是 Windows 换行符,
# 先执行: sed -i 's/\r$//' /mnt/usb/collect-diag.sh 再重新跑。
# ============================================================================
# 故意不用 set -e:要的就是“某条命令失败也继续,尽量多采集”。
SELF_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo /tmp)"
TS="$(date +%Y%m%d-%H%M 2>/dev/null || echo manual)"
OUT="${1:-$SELF_DIR/diag-report-$TS.txt}"
# 若脚本所在目录不可写(U 盘只读挂载等),退回 /tmp
if ! ( : > "$OUT" ) 2>/dev/null; then
OUT="/tmp/diag-report-$TS.txt"
: > "$OUT" 2>/dev/null
fi
STACK_HOME="${STACK_HOME:-/opt/llm-stack}"
COMPOSE_DIR="${COMPOSE_DIR:-$STACK_HOME/compose}"
HEALTH_DIR="${HEALTH_DIR:-/data/runtime/health}"
sec() {
{
echo ""
echo "=================================================================="
echo "### $*"
echo "=================================================================="
} | tee -a "$OUT"
}
run() { # run "一条完整命令(可含管道)"
{
echo ""
echo "----- \$ $* -----"
} >> "$OUT"
eval "$*" >> "$OUT" 2>&1 || echo "[命令返回非0或不存在,已跳过]" >> "$OUT"
}
echo "诊断报告将写入: $OUT"
echo "采集中,请稍候(约 20-40 秒)..."
sec "0. 基本信息"
run "date"
run "hostname"
run "uptime"
run "uname -a"
run "head -3 /etc/os-release"
sec "1. NVIDIA 驱动版本"
run "nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1"
run "cat /proc/driver/nvidia/version"
sec "2. ★GPU 同构性总表(最关键:8 张卡的 name/显存/算力/device_id 是否完全一致)"
run "nvidia-smi --query-gpu=index,name,memory.total,memory.used,pstate,power.draw --format=csv"
run "nvidia-smi --query-gpu=index,name,compute_cap,vbios_version,serial,pci.bus_id,pci.device_id --format=csv"
sec "3. nvidia-smi -q 全量(含每卡 Product Name / Product Architecture / Board Part Number / Serial)"
run "nvidia-smi -q"
sec "4. PCI 层面看这 8 张卡([10de:xxxx] device-id 不一致即为不同 SKU;并看 PCIe 链路速率/宽度)"
run "lspci -nn | grep -i -E 'nvidia|3d controller'"
for b in $(lspci -nn 2>/dev/null | grep -i -E 'nvidia' | grep -i -E '3d|vga' | awk '{print $1}'); do
{
echo ""
echo ">>> PCI $b <<<"
lspci -vvv -s "$b" 2>/dev/null | grep -i -E 'controller|LnkCap:|LnkSta:|ACSCtl'
} >> "$OUT" 2>&1
done
sec "5. GPU 互联拓扑(本次无 NVLink/纯 PCIe,看 P2P 路径)"
run "nvidia-smi topo -m"
sec "6. dmesg 中的 NVRM / Xid 报错(硬件或驱动层错误,最能暴露坏卡/掉卡)"
run "dmesg -T 2>/dev/null | grep -i -E 'nvrm|xid|nvidia' | tail -150"
sec "7. 所有容器状态"
run "docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'"
sec "8. ★实际部署参数 .env(看真实 DS_UTIL / 策略 / maxlen / 各小模型 util)"
run "cat '$COMPOSE_DIR/.env'"
sec "9. ★DeepSeek 容器实际启动命令 + 退出状态(ExitCode/OOMKilled/Error)"
run "docker inspect vllm-public-llm --format '镜像: {{.Config.Image}}'"
run "docker inspect vllm-public-llm --format '启动命令: {{json .Config.Cmd}}'"
run "docker inspect vllm-public-llm --format '退出码={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}} 重启次数={{.RestartCount}}'"
run "docker inspect vllm-public-llm --format '环境变量:{{println}}{{range .Config.Env}}{{println .}}{{end}}'"
sec "10. ★★DeepSeek 完整根因日志(真正的 AssertionError 所在,即 'see root cause above' 的 above)"
if [ -f "$HEALTH_DIR/vllm-public-llm.log" ]; then
run "tail -n 400 '$HEALTH_DIR/vllm-public-llm.log'"
fi
run "docker logs vllm-public-llm --tail 400 2>&1"
sec "11. 其它未恢复服务日志尾部(连锁排查用)"
for c in litellm-public litellm-gc litellm-smt vllm-gc-embed vllm-gc-rerank vllm-smt-embed vllm-smt-rerank vllm-smt-llm nginx-gateway; do
{
echo ""
echo ">>> $c <<<"
docker logs "$c" --tail 40 2>&1 || echo "[无此容器或无日志]"
} >> "$OUT" 2>&1
done
sec "12. 共生小服务健康状态一览"
run "docker ps -a --format '{{.Names}}\t{{.Status}}' | grep -E 'embed|rerank|ocr|smt-llm|public-llm'"
sec "13. systemd 栈服务"
run "systemctl status llm-stack.service --no-pager -l 2>/dev/null | head -30"
sec "14. /data 挂载与磁盘占用(确认数据盘在位、未写满)"
run "df -h /"
run "df -h /data"
run "mountpoint /data"
run "findmnt /data"
sec "15. conda 环境(mlops 是否创建成功)"
run "ls -1 /opt/miniconda3/envs 2>/dev/null"
{
echo ""
echo "=================================================================="
echo "采集完成 → $OUT"
echo "把这个 txt 拷回来发给工程师即可。"
echo "=================================================================="
} | tee -a "$OUT"
2.预留空间stop-models-for-235b.sh:
#!/usr/bin/env bash
# ============================================================================
# stop-models-for-235b.sh — 停掉全部 8 个 vLLM 模型容器,腾空所有 GPU 给 235B 测试
# - 只停吃显存的 vLLM 容器;nginx / litellm / 监控 / webui / 控制台 保持运行(不吃显存)。
# - 用 docker stop(可逆):之后 docker start <名字> 即可逐个起回来,再自行编排落卡。
# - 诊断自愈按安全策略【不会】自动重建 vLLM,停了就是停了,放心。
#
# 用法: sudo bash stop-models-for-235b.sh
# ============================================================================
set -uo pipefail
VLLM="vllm-public-llm vllm-gc-embed vllm-gc-rerank vllm-gc-ocr vllm-smt-llm vllm-smt-embed vllm-smt-rerank vllm-smt-ocr"
echo "===== 停止全部 vLLM 模型容器 ====="
for c in $VLLM; do
if docker ps --format '{{.Names}}' | grep -qx "$c"; then
docker stop "$c" >/dev/null 2>&1 && echo " 已停 $c" || echo " 停止失败 $c"
else
echo " 跳过(本就未运行) $c"
fi
done
echo "===== 等 8 秒让显存释放 ====="
sleep 8
echo "===== 各卡显存占用(应都接近 0 MiB)====="
nvidia-smi --query-gpu=index,name,memory.used,memory.total --format=csv
echo ""
echo "GPU 已腾空。下一步建议:"
echo " 1) 测 235B(4 张好卡 TP=4):"
echo " sudo bash set-235b-tp4.sh \"0,1,3,5\" # 换成你确认的 4 张好卡"
echo " docker logs -f vllm-public-llm # 看是否 Application startup complete"
echo ""
echo " 2) 235B 起好后,按需逐个起回小模型(落到你想要的卡上再编排):"
echo " docker start vllm-gc-embed # 其余同理"
echo " 可选: vllm-gc-embed vllm-gc-rerank vllm-gc-ocr vllm-smt-llm vllm-smt-embed vllm-smt-rerank vllm-smt-ocr"
echo ""
echo " 3) 调整某个小模型落哪几张卡 / util:"
echo " sudo /opt/llm-stack/runtime/place-gpus.sh # 搬家(落卡)"
echo " sudo /opt/llm-stack/runtime/tune-util.sh show # 看/调显存占比"
echo " 或用控制台⑥(GPU 编排)。"
echo ""
echo "注:此时网关对未启动的服务会返回 502,属测试期正常现象。"
echo "全部恢复:把上面 8 个 docker start 起一遍即可;或重启机器由开机自启拉起。"
3。设置模型占卡set-235b-tp4.sh:
#!/usr/bin/env bash
# ============================================================================
# set-235b-tp4.sh — 把公用方 235B 从 TP=8 改成 TP=4,落到 4 张好卡
# 为什么 TP=4:你这个是 FP8 块量化(块=128)版,1536÷8=192 不整除 → TP=8 数学上
# 不可行;1536÷4=384÷128=3 整除 → TP=4 可行。
# 注意:TP=4 时每卡权重≈55G(TP=8 的两倍),util 必须够大,默认 0.85。
# ⚠ 请尽量选 4 张【好卡且其上没有大占用】的卡;若 OOM,降低 util 或换卡。
#
# 用法(机器上):
# sudo bash set-235b-tp4.sh "0,1,3,5" # 引号内填 4 张好卡(0-7)
# sudo bash set-235b-tp4.sh "0,1,3,5" 0.80 # 第二个参数自定 util(OOM 时调小)
# ============================================================================
set -uo pipefail
CARDS="${1:-0,1,3,5}"
UTIL="${2:-0.85}"
COMPOSE=/opt/llm-stack/compose
TS=$(date +%Y%m%d-%H%M)
n=$(echo "$CARDS" | tr ',' '\n' | grep -c .)
[ "$n" = "4" ] || { echo "必须正好 4 张卡,你给的是 $n 张:$CARDS"; exit 1; }
[ -f "$COMPOSE/docker-compose.fallback-235b.yml" ] || { echo "没找到 235B 叠加层,请先 sudo /opt/llm-stack/runtime/switch-to-235b.sh 切到 235B 再跑本脚本。"; exit 1; }
cd "$COMPOSE" || exit 1
cp -n .env ".env.bak-$TS"
# 1) TP=4 + 抬高 util(TP=4 每卡权重翻倍,原 0.40 装不下)
grep -q '^Q235_TP=' .env && sed -i "s/^Q235_TP=.*/Q235_TP=4/" .env || echo "Q235_TP=4" >> .env
grep -q '^Q235_UTIL=' .env && sed -i "s/^Q235_UTIL=.*/Q235_UTIL=$UTIL/" .env || echo "Q235_UTIL=$UTIL" >> .env
# 2) 落卡:用一个小叠加层覆盖 NVIDIA_VISIBLE_DEVICES(不动原 overlay)
cat > docker-compose.235btp4.yml <<EOF
services:
vllm-public-llm:
environment:
NVIDIA_VISIBLE_DEVICES: "$CARDS"
CUDA_DEVICE_ORDER: "PCI_BUS_ID"
EOF
echo "[*] Q235_TP=4 Q235_UTIL=$UTIL 卡=$CARDS"
echo "[*] 重建 vllm-public-llm(三层叠加:base + fallback-235b + 235btp4)..."
docker compose -f docker-compose.yml -f docker-compose.fallback-235b.yml -f docker-compose.235btp4.yml up -d --force-recreate vllm-public-llm
echo ""
echo "[*] 235B 加载约 3-8 分钟,实时看日志:"
echo " docker logs -f vllm-public-llm"
echo " 成功标志:出现 'Application startup complete',docker ps 里 vllm-public-llm 为 healthy。"
echo " 若日志再出现 '... not divisible by ... 128' = 卡数仍非 4,检查 NVIDIA_VISIBLE_DEVICES。"
echo " 若 OOM/显存不足 = 同卡有小模型抢显存,重跑并把 util 调小,例如:sudo bash set-235b-tp4.sh \"$CARDS\" 0.70"
echo ""
echo "回滚:cp $COMPOSE/.env.bak-$TS $COMPOSE/.env; rm $COMPOSE/docker-compose.235btp4.yml; sudo /opt/llm-stack/runtime/switch-to-235b.sh"
4.修复配置重启动make-235b-persistent.sh, 步骤: sudo bash set-235b-tp4.sh "0,1,3,5" # 1) 切 235B TP=4 落好卡(生成两个叠加层) sudo bash make-235b-persistent.sh # 2) 打补丁,让重启也带上这两个叠加层 sudo bash /opt/llm-stack/runtime/stack-up.sh # 3) 不用真重启,跑一遍验证幂等 docker ps | grep public-llm # 仍是 235B/healthy 就对了:
#!/usr/bin/env bash
# ============================================================================
# make-235b-persistent.sh — 让 235B 配置扛得住重启
# 坑:开机自启的 stack-up.sh 默认只加载 docker-compose.yml + docker-compose.placement.yml,
# 【不加载】235B 的 fallback-235b.yml,也不加载 set-235b-tp4.sh 生成的 235btp4.yml。
# 不补这一刀,重启后公用方会退回 DeepSeek、落卡也丢。
# 做法:给 stack-up.sh 打补丁——这两个叠加层"文件存在即开机自动带上",且顺序正确
# (base -> placement -> fallback-235b -> 235btp4,最后者覆盖落卡)。幂等,可重复跑。
#
# 跑这个之前请先跑过 set-235b-tp4.sh(它会生成那两个叠加层)。
# 用法: sudo bash make-235b-persistent.sh
# ============================================================================
set -uo pipefail
SU=/opt/llm-stack/runtime/stack-up.sh
TS=$(date +%Y%m%d-%H%M)
[ -f "$SU" ] || { echo "找不到 $SU"; exit 1; }
echo "===== 打补丁前:stack-up.sh 当前加载逻辑 ====="
grep -nE 'docker-compose.*\.yml|files\+?=' "$SU" || true
cp -n "$SU" "$SU.bak-$TS"
# 先去掉可能已存在的这两行(避免重复/顺序乱),再在 placement 行后按正确顺序插入
awk '
index($0, "docker-compose.fallback-235b.yml ]] && files+=") > 0 { next }
index($0, "docker-compose.235btp4.yml ]] && files+=") > 0 { next }
{ print }
index($0, "docker-compose.placement.yml ]] && files+=") > 0 {
print "[[ -f docker-compose.fallback-235b.yml ]] && files+=(-f docker-compose.fallback-235b.yml)"
print "[[ -f docker-compose.235btp4.yml ]] && files+=(-f docker-compose.235btp4.yml)"
}
' "$SU" > "$SU.tmp" && mv "$SU.tmp" "$SU"
chmod +x "$SU"
echo ""
echo "===== 打补丁后 ====="
grep -nE 'docker-compose.*\.yml|files\+?=' "$SU" || true
echo ""
if grep -q 'fallback-235b.yml ]] && files' "$SU"; then
echo "成功:开机/重启时会自动带上 fallback-235b + 235btp4,235B 与落卡不再丢。"
echo "不必真重启即可幂等验证(它会按补丁后的叠加层重拉一遍):"
echo " sudo bash $SU"
echo " docker inspect vllm-public-llm --format '{{json .Config.Cmd}} NVD={{range .Config.Env}}{{println .}}{{end}}' | grep -iE 'qwen235|VISIBLE'"
else
echo "⚠ 没找到 placement 锚点行——你机器上的 stack-up.sh 版本可能不同。"
echo " 把 $SU 的内容发我,我给你手改。回滚: cp $SU.bak-$TS $SU"
fi
5.按需拉取: docker start vllm-gc-embed 等等,或者place-gpus.sh/tune-util.sh
6.修复litellm,fix-litellm.sh
#!/usr/bin/env bash
# ============================================================================
# fix-litellm.sh — 修三套 litellm 离线起不来(502 的根)
# 根因:configs/litellm/{public,gc,smt}.yaml 里 enable_prometheus: true,
# 离线镜像缺 prometheus 依赖 → worker 一启动就崩、三个一起死 → 4000 没人听 →
# nginx 连不上报 Connection refused → 凡走 litellm 的(embed/chat/ocr)全 502。
# 修法:关掉 enable_prometheus,重启三个 litellm,再刷新 nginx 上游缓存。
#
# 用法(机器上): sudo bash fix-litellm.sh
# 若报 $'\r' 错: sed -i 's/\r$//' fix-litellm.sh 再跑。
# ============================================================================
CFG=/opt/llm-stack/configs/litellm
TS=$(date +%Y%m%d-%H%M)
OUT=/tmp/litellm-fix-$TS.log
echo "===== 0. 留底:修复前真实报错(stderr,健康日志里没有的那段) =====" | tee "$OUT"
docker logs litellm-public --tail 150 >> "$OUT" 2>&1
echo "(已存 $OUT)"
echo "===== 1. 关闭 enable_prometheus(三套配置,改前自动备份) ====="
for f in public gc smt; do
y="$CFG/$f.yaml"
[ -f "$y" ] || { echo " 跳过(无 $y)"; continue; }
cp -n "$y" "$y.bak-$TS"
sed -i 's/enable_prometheus:[[:space:]]*true/enable_prometheus: false/I' "$y"
printf " %-12s -> " "$f.yaml"; grep -i enable_prometheus "$y" || echo "(本无此项)"
done
echo "===== 2. 重启三个 litellm(重读配置) ====="
docker restart litellm-public litellm-gc litellm-smt
echo " 等 25 秒让它们起来..."
sleep 25
echo "===== 3. 刷新 nginx 上游 IP 缓存(消除 502) ====="
docker restart nginx-gateway
sleep 6
echo "===== 4. 验证 ====="
docker ps --format '{{.Names}}\t{{.Status}}' | grep -E 'litellm|nginx' | tee -a "$OUT"
echo "--- 经 8080 网关(200=通) ---" | tee -a "$OUT"
for p in public gc smt; do
curl -s -o /dev/null -w " /v1/$p/models HTTP=%{http_code}\n" "http://localhost:8080/v1/$p/models" | tee -a "$OUT"
done
echo "===== 5. 修复后日志尾 ====="
docker logs litellm-public --tail 30 2>&1 | tee -a "$OUT"
echo ""
echo "三个 litellm 变 healthy 且 HTTP=200 即成功;网页/opencode 随即可用。"
echo "若仍失败:把 $OUT 带回来(里面有真实报错)。"
echo "回滚:cp $CFG/public.yaml.bak-$TS $CFG/public.yaml(gc/smt 同理)后 docker restart litellm-public litellm-gc litellm-smt。"
7,修复文档:fix-delivery-doc.sh
#!/usr/bin/env bash
# ============================================================================
# fix-delivery-doc.sh — 在交付文档最前面插入"现场实测勘误",更正自动生成的错误内容
# 作用对象:/opt/llm-stack/DELIVERY.md 及其副本 /opt/llm-stack/docs/DELIVERY.md
# 做法:不去 sed 改正文(易改坏),而是在最顶部插入一段权威勘误,注明"与下文冲突
# 以本节为准"。可重复运行(带 BEGIN/END 标记,重跑会先删旧勘误再插新的)。
#
# 用法:先按你【最终跑通的状态】改下面几个变量,再:
# sudo bash fix-delivery-doc.sh
# 若报 $'\r':sed -i 's/\r$//' fix-delivery-doc.sh 再跑。
# ============================================================================
# ===================== 按现场最终状态填写(改这里) =====================
PUBLIC_MODEL="Qwen3-235B-A22B-Thinking-2507" # 公用方最终实际用的模型
PUBLIC_TP="4" # 公用方并行度(235B-FP8 只能 4)
PUBLIC_CARDS="0,1,3,5" # 公用方落的 4 张好卡
BAD_CARDS="2,4" # 确认有问题、不可用于大模型的卡(显示 Graphics Device)
SMT_LLM_CARDS="6,7" # SMT 27B 已搬到的卡
# =======================================================================
set -uo pipefail
TS=$(date +%Y%m%d-%H%M)
DOCS=(/opt/llm-stack/DELIVERY.md /opt/llm-stack/docs/DELIVERY.md)
read -r -d '' ERRATA <<EOF || true
<!-- ERRATA-BEGIN -->
# ★ 现场实测勘误(更新于 ${TS},与下文冲突处以本节为准)
> 本文件下半部分由安装脚本在**首启前**自动生成,部分内容是按设计预期写的;经现场
> 8×H100 实测,以下几处需更正。**实际交付状态以本节为准。**
## 1. 硬件勘误
- 8 张卡中 **${BAD_CARDS} 号为非标/有问题卡**:\`nvidia-smi\` 显示为 \`NVIDIA Graphics Device\`(非 H100)、功率 700W(SXM 芯片魔改 PCIe),不参与任何大模型部署。
- 本批 H100 **未配 NVLink 桥**,且为**双路服务器跨槽(topo 显示 SYS)**,卡间走 PCIe,无 NVLink。
- 已用 \`/etc/modprobe.d/disable-nvlink.conf\`(\`NVreg_NvLinkDisable=1\`)关闭 NVLink;如需恢复:删该文件 + \`update-initramfs -u\` + 重启。
## 2. 公用大模型勘误(最重要)
- 文档下文写"公用方 = DeepSeek-V4-Flash、占满 8 卡、tp8-ep"——**在本机起不到**:无 NVLink 导致专家并行(EP)的 all-to-all 失败,且 dev 镜像 torch.compile 报 InductorError。
- **实际公用方已改为:\`${PUBLIC_MODEL}\`,TP=${PUBLIC_TP},落卡 ${PUBLIC_CARDS}。**
- 若仍要用 DeepSeek:必须加 \`--enforce-eager\`(关 torch.compile)+ 改 tp4 + 落到好卡,且 **不要用 dp-ep**(仍带 EP,会同样失败)。无 NVLink 机器策略阶梯应为 **tp8-ep → tp8 → tp4**(跳过 dp-ep)。
## 3. Qwen3-235B(FP8)勘误
- 此为 **FP8 块量化(块=128)版,只能 TP=4**:1536÷8=192 不被 128 整除,**TP=8 在数学上不可行**(vLLM 直接报 \`not divisible by ... block_n = 128\`)。
- 入口脚本/文档中"TP=8 默认、别改小"的说法对**本 FP8 版无效**,请按 TP=4 使用。
## 4. SMT 对话模型(Qwen3.6-27B)勘误
- 已从问题卡搬到 **${SMT_LLM_CARDS} 号卡**,运行正常。
## 5. 监控勘误
- 三套 litellm 配置已**关闭 \`enable_prometheus\`**(离线镜像缺 prometheus 依赖会导致 litellm worker 崩溃、进而全网关 502)。
- 影响:Grafana 里 **litellm 层**的请求/延迟/token 面板无数据;**vLLM 引擎自身指标、所有推理/路由/网页/API 功能均不受影响**。联网补齐依赖后可改回 \`true\` 恢复。
## 6. 本次现场修复清单
- 关闭 litellm \`enable_prometheus\` 并重启三套 litellm + nginx → 解决全网关 502。
- 公用方改为 ${PUBLIC_MODEL} TP=${PUBLIC_TP}(避开 ${BAD_CARDS} 号坏卡)。
- SMT 27B 迁至 ${SMT_LLM_CARDS} 号卡。
<!-- ERRATA-END -->
EOF
for DOC in "${DOCS[@]}"; do
[ -f "$DOC" ] || { echo "跳过(不存在):$DOC"; continue; }
cp -n "$DOC" "$DOC.bak-$TS"
# 重跑:先删旧勘误块(BEGIN..END),避免重复插入
sed -i '/<!-- ERRATA-BEGIN -->/,/<!-- ERRATA-END -->/d' "$DOC"
tmp=$(mktemp)
{ printf '%s\n\n' "$ERRATA"; cat "$DOC"; } > "$tmp" && mv "$tmp" "$DOC"
echo "已更新:$DOC (原文件备份 $DOC.bak-$TS)"
done
echo ""
echo "完成。打开 /opt/llm-stack/DELIVERY.md,最顶部即为现场勘误。"
echo "如需重改:改本脚本顶部变量后再跑一次即可(会自动替换旧勘误)。"
8.可选修复端口:
#!/usr/bin/env bash
# ============================================================================
# fix-litellm-port.sh — 排查并修复 litellm 因端口被占用起不来
# litellm 容器内部监听 4000;宿主机映射:4001(public) / 4002(gc) / 4003(smt)。
# 端口冲突典型现象:容器 Exited、报 "port is already allocated" / "address already in use"。
# 常见原因:旧/重复容器没删干净仍占着端口;或宿主机别的进程占了。
#
# 用法:
# sudo bash fix-litellm-port.sh # 只诊断(安全,只读)
# sudo bash fix-litellm-port.sh --fix # 诊断 + 删掉重名旧 litellm 容器并重建
# ============================================================================
set -uo pipefail
FIX="${1:-}"
COMPOSE=/opt/llm-stack/compose
echo "===== 1. 宿主机谁在听 4000-4003(带进程/pid)====="
if command -v ss >/dev/null 2>&1; then
ss -ltnp 2>/dev/null | grep -E ':400[0-3]\b' || echo " (4000-4003 没有监听)"
else
netstat -ltnp 2>/dev/null | grep -E ':400[0-3]\b' || echo " (4000-4003 没有监听)"
fi
echo ""
echo "===== 2. 所有 litellm 相关容器(含已退出/重复的,看端口绑定)====="
docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -iE 'litellm|:400[0-3]' || echo " (无)"
echo ""
echo "===== 3. 三个 litellm 的启动错误 / 端口报错 ====="
for c in litellm-public litellm-gc litellm-smt; do
st=$(docker inspect "$c" --format '{{.State.Status}} {{.State.Error}}' 2>/dev/null || echo "无此容器")
echo " $c -> $st"
docker logs "$c" 2>&1 | grep -iE 'address already in use|port is already allocated|bind.*fail' | tail -2 | sed 's/^/ /'
done
if [ "$FIX" != "--fix" ]; then
cat <<EOF
只诊断完成。判断与手动修法:
· 若上面没看到 "already allocated/in use" → 不是端口问题(很可能是 enable_prometheus,用 fix-litellm.sh)。
· 占端口的是某个【旧 docker 容器】(第2节里多出来的): docker rm -f <那个容器名>
· 占端口的是【宿主机进程】(第1节里有 pid=NNN):先确认不是要紧服务,再 kill NNN
· 清完后重建三个 litellm: sudo bash $0 --fix
EOF
exit 0
fi
echo ""
echo "===== 4. 自动修:删掉重名 litellm 容器,按当前叠加层重建 ====="
cd "$COMPOSE" || { echo "找不到 $COMPOSE"; exit 1; }
# 235B 切换会让 litellm-public 带 fallback 叠加层,重建时要带上
files=(-f docker-compose.yml)
[ -f docker-compose.fallback-235b.yml ] && files+=(-f docker-compose.fallback-235b.yml)
echo " 用叠加层:${files[*]}"
docker rm -f litellm-public litellm-gc litellm-smt 2>/dev/null || true
docker compose "${files[@]}" --env-file .env up -d litellm-public litellm-gc litellm-smt
echo " 等 20 秒..."; sleep 20
docker restart nginx-gateway; sleep 5
echo ""
echo "===== 5. 验证 ====="
docker ps --format '{{.Names}}\t{{.Status}}' | grep -E 'litellm|nginx'
for p in public gc smt; do
curl -s -o /dev/null -w " /v1/$p/models HTTP=%{http_code}\n" "http://localhost:8080/v1/$p/models"
done
echo ""
echo "若第4步重建后仍报端口占用 = 占端口的不是这三个容器,而是【别的容器或宿主机进程】。"
echo "回到第1、2节找到真正占用者(看 pid / 容器名),docker rm -f 它 或 kill 它,再重跑 --fix。"
9.修复deepseek问题: sed -i '/--enable-auto-tool-choice/a\ --enforce-eager' /opt/llm-stack/configs/vllm/deepseek-launch.sh sudo /opt/llm-stack/runtime/switch-llm.sh ds tp4 docker logs -f vllm-public-llm 10.其他 手动处理litevllm问题: sed -i 's/enable_prometheus: true/enable_prometheus: false/' /opt/llm-stack/configs/litellm/{public,gc,smt}.yaml docker restart litellm-public litellm-gc litellm-smt nginx-gateway
验证(可选,第三条): curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/v1/gc/models 出 200 就成了。
想留后路就先抄一条备份(可选): cp /opt/llm-stack/configs/litellm/{public,gc,smt}.yaml /tmp/ 验证跑起来测试:
① 先看模型有没有注册上:
curl -s http://localhost:8080/v1/public/models
能列出 Qwen3-235B-A22B-Thinking-2507 就对了。
② 真正发一句对话测:
curl http://localhost:8080/v1/public/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3-235B-A22B-Thinking-2507","messages":[{"role":"user","content":"你好,简单介绍下你自己"}]}'
返回 JSON 里有 content 正文就是通了(Thinking 模型会先有思考段,正常)。
③ 如果上面 502/不通,绕开网关直连容器看是模型还是网关的问题:
docker exec vllm-public-llm curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"Qwen3-235B-A22B-Thinking-2507","messages":[{"role":"user","content":"你好"}]}'