- register yaml 1.2 float resolver so scientific notation (2e-5) becomes float, not str - replaces the decimal-point workaround in train configs - add containerized training doc under docs/developer
4.0 KiB
4.0 KiB
Containerized Training Deployment
Rules for running AstrAI distributed training in containers, distilled from real deployment failures. Read before touching Dockerfile, docker-compose.yml, scripts/train.sh, train-entrypoint.sh. AGENTS.md mirrors this locally; this file is the committed version.
Architecture
scripts/train.sh host-side CLI: env loading, preflight, compose wrapper, lifecycle
└── docker-compose.yml GPU passthrough, mounts, in-container env vars, entrypoint
└── train-entrypoint.sh GPU-count resolution, parallel-mode selection, auto-resume
└── train.py --config /run/astrai/train.yaml
| Layer | Responsible for | NOT responsible for |
|---|---|---|
train.sh |
host paths, .env.train, preflight, lifecycle |
training args, GPU selection, parallel mode |
| compose | GPU passthrough, mounts, in-container env (NCCL) | training args (beyond TRAIN_* forwarding) |
| entrypoint | --ckpt_dir/--nprocs/--parallel_mode/--param_path, resume |
hyperparameters (YAML/CLI) |
train.yaml |
hyperparameters (_merge_yaml_into_kwargs, CLI wins) |
container paths, process count |
Path Conventions
| Host var | Container | Perm | Purpose |
|---|---|---|---|
TRAIN_DATA_DIR |
/data |
ro | dataset (data_root_path must be /data) |
TRAIN_MODEL_DIR |
/models/base |
ro | base model (config.json + model.safetensors) |
TRAIN_CHECKPOINT_DIR |
/checkpoints |
rw | checkpoint root, per-TRAIN_JOB_NAME subdirs |
TRAIN_CONFIG_FILE |
/run/astrai/train.yaml |
ro | training YAML (mounted only on start) |
| code | /app |
image | not a mount; rebuild image for code changes |
Hard Rules
- Filter GPUs once: compose passes the full physical set (
count: all);CUDA_VISIBLE_DEVICESfilters inside by physical index. Nevercount: N+ physical indices (double filter leaves 1 card →device_id out of range). - In-container UID = host UID: Dockerfile builds the user via
USER_UID/USER_GIDargs;train.shinjectsASTRAI_UID/GID(bashUIDis readonly). composeuser:alone does not create the /etc/passwd entry — torch'sgetpass.getuser()then dies withuid not found. - In-container env vars are explicit:
.env.train(--env-file) is only compose's interpolation dictionary — never reaches the container. A var arrives only via a value-lessenvironmententry (- VAR, read from the calling process env). - NCCL hang workaround (this host):
NCCL_P2P_DISABLE=1+NCCL_NET_GDR_LEVEL=0must be in-container. - Checkpoint complete =
meta.json + config.json + model.safetensors + optimizer.pt + scheduler.pt;startauto-resumes the latest complete one. - tqdm is silent without a TTY: add
disable=Falseinastrai/trainer/train_callback.py;metric.jsonl(per step) works as progress evidence regardless.
Operations
bash scripts/train.sh init # first run: dirs + .env.train (edit per machine)
bash scripts/train.sh preflight # validate Docker/paths/GPU/model/YAML/compose
bash scripts/train.sh start # build + start in background (auto-resume)
bash scripts/train.sh start --foreground -- --dry-run # print plan only
bash scripts/train.sh logs | status | stop | restart
bash scripts/train.sh clean --keep 5 # prune old checkpoints (--force to delete)
Files
docker-compose.yml— trainer service:count: all,ASTRAI_UID/GIDbuild args +user:, env whitelist, mountsDockerfile— production stage builds user fromUSER_UID/USER_GID;ENV HOME=/home/astrai;USER astraiscripts/train.sh—load_envfiltersUID=lines (readonly var);compose()injectsASTRAI_UID/GIDscripts/docker/train-entrypoint.sh— GPU-count resolution, parallel mode, resume.env.train,train.yaml— host-specific; templates fromscripts/train.sh init; scientific-notation floats (2e-5) parse correctly since train.py uses the YAML 1.2 float schema