Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw History Blame Contribute Delete
5.19 kB
"""Axolotl SFT/LoRA backend.
Real subprocess wrapper around `accelerate launch -m axolotl.cli.train`. Steps:
1. Compile `XTrainConfig` + `AutotunePlan` to an Axolotl YAML via
`mindxtrain.train.axolotl_compile.compile_axolotl_yaml`.
2. Inject MI300X env vars from the autotune plan.
3. Spawn the subprocess; tee stdout/stderr to `runs/<run_id>/train.log`.
4. Return the checkpoint directory.
Two entry points share the same prep helpers (`prepare_run`):
- `run_axolotl(cfg, plan, out_dir)` — synchronous; blocks until completion;
used by the `mindxtrain train` CLI verb.
- `prepare_run(cfg, plan, out_dir)` — returns the cmd + env + paths so the
Coach UI's streaming launch path can hand them to
`mindxtrain.operator.runs.spawn_subprocess_streaming`.
"""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
from collections.abc import Callable
from dataclasses import dataclass
from pathlib import Path
import yaml
from mindxtrain.autotune.plan import AutotunePlan
from mindxtrain.config.schema import XTrainConfig
from mindxtrain.train.axolotl_compile import compile_axolotl_yaml
_BASE_ENV = {
"PYTORCH_ROCM_ARCH": "gfx942",
"HSA_NO_SCRATCH_RECLAIM": "1",
"HIP_FORCE_DEV_KERNARG": "1",
"GPU_MAX_HW_QUEUES": "1",
}
def _accelerate_available() -> bool:
return shutil.which("accelerate") is not None
def _plan_env(plan: AutotunePlan) -> dict[str, str]:
env: dict[str, str] = dict(_BASE_ENV)
if plan.rccl_config == "8gpu_xgmi":
env["NCCL_MIN_NCHANNELS"] = "112"
if plan.attention_backend == "ck":
env["NVTE_CK_USES_BWD_V3"] = "1"
env["NVTE_CK_IS_V3_ATOMIC_FP32"] = "1"
env["PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32"] = "1"
return env
@dataclass(frozen=True)
class PreparedRun:
"""Resolved cmd + env + paths for an Axolotl run.
The streaming launch path consumes this directly; the synchronous
`run_axolotl` does too.
"""
cmd: list[str]
env: dict[str, str]
yaml_path: Path
log_path: Path
checkpoint_dir: Path
def prepare_run(cfg: XTrainConfig, plan: AutotunePlan, out_dir: Path) -> PreparedRun:
"""Compile YAML, materialize on disk, and return the cmd/env/paths.
Does NOT spawn the subprocess. Raises `RuntimeError` if `accelerate` is
not on PATH (the same condition the synchronous wrapper checks).
"""
if not _accelerate_available():
msg = (
"accelerate not found on PATH; install with `uv sync --extra ml` "
"and ensure the `axolotl` package is reachable in the same venv."
)
raise RuntimeError(msg)
out_dir = Path(out_dir)
out_dir.mkdir(parents=True, exist_ok=True)
yaml_payload = compile_axolotl_yaml(cfg, plan)
yaml_path = out_dir / f"{cfg.meta.run_name}.axolotl.yaml"
yaml_path.write_text(yaml.safe_dump(yaml_payload, sort_keys=False))
log_path = out_dir / "train.log"
env = dict(os.environ)
env.update(_plan_env(plan))
cmd = [
"accelerate",
"launch",
"-m",
"axolotl.cli.train",
str(yaml_path),
]
return PreparedRun(
cmd=cmd,
env=env,
yaml_path=yaml_path,
log_path=log_path,
checkpoint_dir=out_dir / yaml_payload.get("output_dir", "checkpoint"),
)
def _run_streaming(
*,
cmd: list[str],
env: dict[str, str],
log_path: Path,
on_line: Callable[[str], None],
) -> int:
"""Run `cmd`, tee each stdout line to `log_path` and call `on_line(line)`.
Returns the subprocess return code. Used by the synchronous CLI path;
the Coach UI streaming launch path uses
`mindxtrain.operator.runs.spawn_subprocess_streaming` instead, which
runs the reader in a thread.
"""
log_path.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("w", buffering=1) as log:
log.write(f"# cmd: {' '.join(cmd)}\n\n")
log.flush()
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
env=env,
text=True,
bufsize=1,
)
assert proc.stdout is not None
for raw in proc.stdout:
log.write(raw)
log.flush()
on_line(raw.rstrip("\n"))
return proc.wait()
def run_axolotl(
cfg: XTrainConfig,
plan: AutotunePlan,
out_dir: Path,
*,
on_line: Callable[[str], None] | None = None,
) -> Path:
"""Run an Axolotl training job and return the checkpoint directory.
Blocks until completion. If `on_line` is provided, it is called once per
stdout line (the same lines that get written to `train.log`); the CLI
path passes `None`.
"""
prepared = prepare_run(cfg, plan, out_dir)
sink = on_line if on_line is not None else (lambda _line: None)
rc = _run_streaming(cmd=prepared.cmd, env=prepared.env, log_path=prepared.log_path, on_line=sink)
if rc != 0:
sys.stderr.write(f"axolotl returned {rc}; see {prepared.log_path}\n")
raise SystemExit(rc)
return prepared.checkpoint_dir
__all__ = ["PreparedRun", "prepare_run", "run_axolotl"]