strata docker目录治理方案

Hermes 的评估不仅致命,而且极具工程深度——它揪出了由于脱离代码具体实现而产生的逻辑断层(特别是 Launcher 路径覆写和 Docker Context 爆炸这两颗“核弹”)。完全采信这份评估。

这里是经过修正、填补了所有漏洞、并附带生命周期与删建耗时注释的终极执行方案。

一、 终极目录架构(带生命周期注释)

/s500data/docker/llama/
├── docker-compose.yml          # Llama 栈
├── strata.yml                  # Strata 栈
├── .env                        
└── data/
    ├── models/                 # [只读] 双栈共享模型 (不可删: 毁灭级网络惩罚)
    ├── config/                 # [只读] 各设备 INI 配置 (不可删: 手写配置丢失)
    └── strata/                 # ★ Strata 高内聚结界 ★
        ├── build/              # [闲置] 引擎升级包 [删建耗时]: up 0耗时,涉及版本升级才会重新build
        │   ├── terminal/       # 完整的 strata_control/python 基础
        │   ├── components/     # 完整的 engine (含 .updates/state.json)
        │   ├── Dockerfile
        │   └── .dockerignore   # 仅需排除 *.tar.gz
        ├── scripts/            # [只读] strata-launcher.py
        ├── downloads/          # [只读] MTP Tensors / DSpark 缓存 [删建耗时]重拉几十GB,几十分钟到数小时
        └── cache/              # [可删] 派生物 (packs, mtp, profiles, launch-presets, logs) # [删建耗时]: 下次up自动重算1~3分钟

二、 核心代码修正

1. data/strata/scripts/strata-launcher.py (深层剔除硬编码)

在头部定义常量后,必须替换后续的 4 处深层引用,否则挂载全废。

头部修改:

import os
from pathlib import Path
STRATA_ROOT = Path(os.environ.get("STRATA_ROOT") or Path(__file__).resolve().parent.parent)
STRATA_INSTALL_DIR = Path(os.environ.get("STRATA_INSTALL_DIR") or STRATA_ROOT / "terminal")
STRATA_DATA_DIR = Path(os.environ.get("STRATA_DATA_DIR") or STRATA_ROOT / "data")
STRATA_PROFILE_DIR = Path(os.environ.get("STRATA_PROFILE_DIR") or STRATA_ROOT / "config" / "profiles")
STRATA_PRESET_DIR = Path(os.environ.get("STRATA_PRESET_DIR") or STRATA_ROOT / "config" / "presets")

正文修改:

  • 第 1 处 launch_argv():将 --data-dir, str(STRATA_ROOT) 改为 --data-dir, str(STRATA_DATA_DIR)
  • 第 2 处 build_env() mkdirs:用新常量替换硬编码。

    # 替换前:(STRATA_ROOT / "data").mkdir(...)
    STRATA_DATA_DIR.mkdir(parents=True, exist_ok=True)
    STRATA_PROFILE_DIR.mkdir(parents=True, exist_ok=True)
    STRATA_PRESET_DIR.mkdir(parents=True, exist_ok=True)
    
  • 第 3 处 build_env() 环境变量 dict:

    # 替换前:"STRATA_INSTALL_DIR": str(TERMINAL)
    "STRATA_INSTALL_DIR": str(STRATA_INSTALL_DIR),
    "STRATA_PRESET_DIR": str(STRATA_PRESET_DIR),
    "STRATA_PROFILE_DIR": str(STRATA_PROFILE_DIR),
    "STRATA_DATA_DIR": str(STRATA_DATA_DIR),
    2. data/strata/build/Dockerfile 与 .dockerignore

将构建上下文缩小至 build/ 目录,彻底避开 23GB 垃圾,并在 template 里补上时间戳。

.dockerignore: (放在 data/strata/build/)

*.tar.gz

Dockerfile:

FROM ghcr.io/ggml-org/llama.cpp:server-cuda

ENV HOME=/home/strata \
    XDG_CONFIG_HOME=/home/strata/.config \
    XDG_DATA_HOME=/opt/strata/share \
    STRATA_ROOT=/opt/strata \
    STRATA_INSTALL_DIR=/opt/strata/terminal \
    STRATA_DATA_DIR=/var/strata \
    STRATA_PROFILE_DIR=/var/strata/profiles \
    STRATA_PRESET_DIR=/var/strata/launch-presets

COPY terminal/ /opt/strata/terminal/
COPY components/ /opt/strata/share/strata/components/

# 补全 data_dir_at 账本字段
RUN mkdir -p /home/strata/.config/strata /var/strata/profiles /var/strata/launch-presets /var/strata/logs && \
    echo '{"data_dir": "/var/strata", "installs": ["/opt/strata/terminal"], "data_dir_at": 1700000000}' > /opt/strata/settings.template.json

WORKDIR /opt/strata

3. data/strata/scripts/strata-entrypoint.sh (全量修正)

修复了 Profile 文件名解析、挂载软链、以及 $@ 透传问题(支持 docker compose run)。

#!/bin/bash
set -euo pipefail

PYTHON="/opt/strata/terminal/python/bin/python3.12"
LAUNCHER="/strata-scripts/strata-launcher.py"

# [放行指令] 允许 docker compose run --rm strata prepare ...
if [ "$#" -gt 0 ] && [ "${1:-}" != "serve" ]; then
    exec "$PYTHON" "$LAUNCHER" "$@"
fi

if [ ! -f "/home/strata/.config/strata/settings.json" ]; then
    cp /opt/strata/settings.template.json /home/strata/.config/strata/settings.json
fi

mkdir -p /var/strata/{packs,mtp,profiles,launch-presets,logs}
ln -sfn /downloads/mtp-tensors /var/strata/mtp/tensors
ln -sfn /downloads/deepseek-v4.1-flash-shared /var/strata/deepseek-v4.1-flash-shared

INI_FILE="/config/${STRATA_INI}.ini"
if [ ! -f "$INI_FILE" ]; then
    echo "[Fatal] INI not found: $INI_FILE"; exit 1
fi

# [修正判据] 解析 preset, family, model
PRESET=$(awk -F'=' '/^[[:space:]]*preset[[:space:]]*=/{sub(/#.*/,"",$2); gsub(/["'\'' ]/,"",$2); print $2}' "$INI_FILE")
FAMILY=$(awk -F '=' '/^[[:space:]]*family[[:space:]]*=/ {gsub(/['\''" ]/, "", $2); print tolower($2)}' "$INI_FILE")
MODEL=$(awk -F '=' '/^[[:space:]]*model[[:space:]]*=/ {gsub(/['\''" ]/, "", $2); print tolower($2)}' "$INI_FILE")

TARGET_PACK="/var/strata/packs/${FAMILY}-${MODEL}"
TARGET_PROFILE="/var/strata/profiles/launch-${PRESET}.json"

if [ ! -d "$TARGET_PACK" ] || [ ! -f "$TARGET_PROFILE" ]; then
    echo "[Strata Auto-Prepare] Cache or Profile missing. Running prepare..."
    if ! "$PYTHON" "$LAUNCHER" prepare "${STRATA_INI}"; then
        echo "[Fatal] Prepare failed. Exiting."; exit 1
    fi
fi

echo "[Strata Serve] Starting engine for ${STRATA_INI}..."
exec "$PYTHON" "$LAUNCHER" serve "${STRATA_INI}"

4. strata.yml 调整项

command 留空(默认走 entrypoint 的 serve),镜像与挂载逻辑与 Hermes 建议完全一致。

services:
  strata:
    image: llama-strata:0.1.48-dsv41
    build:
      context: ./data/strata/build
      dockerfile: Dockerfile
    container_name: strata
    entrypoint: ["/strata-scripts/strata-entrypoint.sh"]
    ports:
      - "${STRATA_PORT}:${STRATA_PORT}"
    
    volumes:
      - ./data/models:/models:ro
      - ./data/config:/config:ro
      - ./data/strata/scripts:/strata-scripts:ro
      - ./data/strata/downloads:/downloads:ro
      - ./data/strata/cache:/var/strata
      - ./data/strata/cache/logs:/home/strata/.local/state
      
    tmpfs:
      - /home/strata/.nv
      - /home/strata/.cache

    environment:
      - CUDA_VISIBLE_DEVICES=0
      - STRATA_PORT=${STRATA_PORT}
      - STRATA_INI=${STRATA_INI}
      - TZ=Asia/Shanghai

    deploy:
      resources:
        limits: cpus: '12.0'; memory: 56G
        reservations:
          cpus: '8.0'; memory: 16G
          devices:
            - driver: cdi
              capabilities: [gpu]
              device_ids: ["nvidia.com/gpu=0"]
    shm_size: 4g
    healthcheck:
      test: ["CMD-SHELL", "curl -fs http://127.0.0.1:${STRATA_PORT}/health >/dev/null || exit 1"]
      interval: 10s; timeout: 5s; retries: 12; start_period: 600s
    networks:
      - ghost_net
    restart: unless-stopped

三、 安全落地的迁移步骤

cd /s500data/docker/llama/data/strata

# 1. 创建目标结构
mkdir -p build downloads cache/logs

# 2. 转移大文件
mv *.tar.gz build/ 2>/dev/null || true
mv mtp/tensors downloads/mtp-tensors 2>/dev/null || true
mv deepseek-v4.1-flash-shared downloads/ 2>/dev/null || true

# 3. 转移构建源头 (保住镜像的可重构性)
mv terminal build/
mv home/.local/share/strata/components build/

# 4. 转移派生物,并【销毁带宿主毒性路径的旧 profile】(必做,阻断 ErroFS)
mv packs cache/ 2>/dev/null || true
mv mtp cache/ 2>/dev/null || true
mv config/launch-presets cache/ 2>/dev/null || true
rm -rf config/profiles   # 【致命消除】销毁旧的绝对路径挂载残留

# 5. 摧毁剩下的无用外壳
rm -rf home engine prebuilt config data

# 6. 回到根目录启动
cd /s500data/docker/llama
docker compose -f strata.yml build
docker compose -f strata.yml up -d

四、 实施结果(2026-10-26 落地,已实跑验证)

上面的方案已全程执行。本节只记实施结果:实际目录终态、实施中相对原方案做的 5 处修正、NixOS 容器必须补的挂载、以及 5 个动态 ini 的冒烟数据。

【落地时间】2026-10-26   镜像 llama-strata:0.1.48-dsv41(13 GB,其中 base llama.cpp server-cuda 占 9.5 GB)

一、目录终态(宿主 data/strata 20 GB;迁移前 23 GB / 11 个目录,现 4 个)
data/strata/
├── build/      4.5G   terminal/(2.6G) + components/(0.93G) + *.tar.gz(1.0G) + Dockerfile + .dockerignore   镜像唯一源
├── scripts/     56K   strata-launcher.py + strata-entrypoint.sh                                            手写,不可删
├── downloads/   13G   mtp-tensors/(4.9G) + deepseek-v4.1-flash-shared/(7.5G)                               贵且只读
└── cache/      3.1G   packs/(1.5G) mtp/(1.6G) profiles/(32K) launch-presets/(12K) state/(156K) models/(软链)  可随意 rm -rf
已删:home/ engine/ prebuilt/ config/ data/(约 8.7 GB 构建残留)+ 过期 dl_strata_models.sh
磁盘可用 441 G(DSpark 与 downloads 同一 inode 硬链,去重后未再变动)

二、实施中相对原方案的 5 处修正(原方案没写,但代码里非改不可)
1. ini 的 api-key-file 改相对路径 4060/4060-api-keys.txt(launcher 相对 STRATA_CONFIG_DIR 解析);原方案写宿主绝对路径,容器内 /config 只读挂载必然失效
2. ini 的 model-path 改相对路径(launcher 相对 STRATA_MODELS_DIR=/models 解析),同上
3. cache/logs 改名 cache/state:strata 把 run config 也写在 $HOME/.local/state/strata/launch/,叫 logs 名不副实
4. entrypoint 的「要不要 prepare」判据不再猜 pack 目录名,改读 strata 自己写的 run config 里的 --pack 实际路径
   (pack 名不是 family-model:gyro 是 gyro-gyro-m,dsv41 是 deepseek-v4.1-flash-ist-das-2.25bpw)
5. entrypoint 的 $@ 透传位置挪到目录组装之后、且 settings.json 补齐之后,否则 docker compose run 进来的 prepare 看不到 /var/strata 结构

三、NixOS 容器必需(不挂 = 763B 档直接 Fatal)
现象:plan 给 placement.verdict=no_card,763B prepare 报
      "Strata can write the settings itself only for the Qwen3.8-Flash-Next, Swift, Coder and Gyro files, not for deepseek41"
      → [Fatal] prepare 失败,不拉起 serve,容器 exit 1 循环重启
根因:strata 的显卡探测调 nvidia-smi(strata_cli/bench_facts.py:149),llama.cpp server-cuda 镜像里没有这个二进制
      → 判 no_card → 走 adopt 兜底 → adopt 不支持 deepseek41 家族
修法(已写入 strata.yml,只读 bind,零拷贝,可逆):
  volumes:      /nix/store:/nix/store:ro
                /run/current-system:/run/current-system:ro
  environment:  PATH=/run/current-system/sw/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
修后实测:容器内 nvidia-smi 报 "0, NVIDIA GeForce RTX 4060, 8188 MiB, 595.71.05"
          verdict no_card → tight;download 仍是 "Nothing to download",7.98 GB 共享件没重下
重要:125B 能跑不代表探测没问题——引擎走 CDI 注入的 libcuda,只有「生成配置」这条分支依赖 nvidia-smi

四、dsv41 共享缓存路径(setup.py 写死,差一层就重下 7.98 GB)
setup.py:dsv41_shared_dir() =  [--models-dir]/[family tag]shared ,而 --models-dir 默认 [data-dir]/models
→ 容器内必须是 /var/strata/models/deepseek-v4.1-flash-shared(entrypoint 已按此建软链)
data/models 下那份同名目录已删:它是 strata 私有缓存,不属于 HF 作者/仓库规范,且与 downloads 那份同 inode

五、5 个动态 ini 冒烟(2026-10-26 实测)
档位                 栈          启动          /v1/models                           对话
4060-qwen3.8-4b      llama.cpp   health 3 s    Qwen3.8-4B                           '可用'(reasoning 241 字)
4060-qwen3.8-9b      llama.cpp   health 3 s    Qwen3.8-9B                           '可用'(reasoning 615 字)
4060-qwen3.8-35b     llama.cpp   health 3 s    Qwen3.8-35B                          '可用'(draft 接受 0.52)
4060-qwen3.8-125b    strata      health 26-32 s qwen3.8-flash-next-gyro-gyro-m      '可用'
4060-qwen3.8-763b    strata      health 24-40 s deepseek-v4.1-flash                  '可用'(25 tok)
每档关闭后显存回落 15 MiB,无残留进程。763B 启动快是 mmap + 按需读 NVMe 专家,不是先把 274 GB 读进内存。
profile + pack + run config 都在时重建容器,Auto-Prepare 次数=0,不再空跑 prepare。

六、常用命令(宿主已无 terminal/,一律容器内执行)
备料:docker compose -f strata.yml run --rm strata prepare 4060/4060-qwen3.8-125b
计划:docker compose -f strata.yml run --rm strata plan    4060/4060-qwen3.8-125b
起停:docker compose -f strata.yml up -d [--force-recreate]    /    docker compose -f strata.yml stop
切档:改 .env 的 STRATA_INI + STRATA_PORT(必须与 ini 的 port 相同,否则 launcher 直接退出)→ up -d --force-recreate
缓存:rm -rf data/strata/cache/* 后 up,entrypoint 自动 prepare;prepare 失败则容器 exit 1,不静默起 serve