- scripts/train.sh load_infra() parses the top-level infra: section of TRAIN_CONFIG_FILE - exports TRAIN_JOB_NAME/DATA/MODEL/CHECKPOINT_DIR/TRAIN_GPU_COUNT/CUDA_VISIBLE_DEVICES - infra overrides .env.train via compose interpolation precedence; keys absent fall back - train.yaml is now the single per-job config: host mounts, GPU filter, and hyperparameters - requires host python3 with PyYAML when TRAIN_CONFIG_FILE is set; errors fail fast - docs: docker-training.md documents the infra overrides and precedence
4.8 KiB
4.8 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 + infra: YAML overrides, 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); top-level infra: host vars |
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). - Host vars can come from
train.yamlinstead:scripts/train.sh load_infra()reads the top-levelinfra:section ofTRAIN_CONFIG_FILE(host side, before the container exists) and exportsTRAIN_JOB_NAME,TRAIN_DATA_DIR,TRAIN_MODEL_DIR,TRAIN_CHECKPOINT_DIR,TRAIN_GPU_COUNT,CUDA_VISIBLE_DEVICES. Compose interpolation prefers the shell environment over--env-file, soinfra:wins; keys absent from it fall back to.env.train. Requires python3 + PyYAML on the host. train.py only merges themodel/data/parallel/training/ckpt/logsections, so theinfrasection is invisible to the trainer. - 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);load_infrareads theinfra:section fromTRAIN_CONFIG_FILE;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;.env.trainis the fallback for host vars not present in theinfra:section