Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw History Blame Contribute Delete
20 kB
"""Pydantic v2 config schema — canonical mindXtrain YAML.
Mirrors the blueprint at docs/blueprints/mindXtrain_ Production Blueprint
for the AMD and lablab.ai Hackathon.md, sections "mindXtrain architecture"
and "Critical code snippets / examples/demo_qwen3_8b_sft.yaml".
Top-level sections:
meta — project / run identity / seed / license
hardware — gpu name + gfx arch + count + HBM
autotune — 60s probe policy (AOT-only — JIT autotune forbidden)
model — base model + attention impl + dtype
data — HF/local dataset + dedupe + sharding
train — backend + method (LoRA/QLoRA/DPO/GRPO/...) + optimizer + env
eval — lm-evaluation-harness + regression detector
quantize — Quark FP8 / MXFP4 / GPTQ-ROCm
serve — vLLM-ROCm / SGLang + reasoning + tool-call parsers
publish — HF + Lighthouse + mindX + AgenticPlace + BANKON + x402
receipt — provenance manifest output
"""
from __future__ import annotations
from pathlib import Path
from typing import Annotated, Literal
from pydantic import BaseModel, ConfigDict, Discriminator, Field, model_validator
# ---- enums / literals -------------------------------------------------------
# gfx9xx = CDNA datacenter (MI-series); gfx10xx/11xx = RDNA consumer Radeon;
# gfx90c = integrated Vega APU (descriptive only — the in-process `trl_local`
# lane never injects the MI300X env vars, so a consumer arch is just metadata).
GfxArch = Literal[
"gfx900", "gfx90c", "gfx942", "gfx950",
"gfx1030", "gfx1100", "gfx1101", "gfx1102", "gfx1103",
]
HardwareName = Literal["mi300x", "mi325x", "mi350x", "mi355x", "consumer_gpu", "local"]
TrainingBackend = Literal["axolotl", "unsloth", "torchtune", "primus", "trl_cpu", "trl_local"]
DType = Literal["bfloat16", "float16", "float32", "fp8_e4m3", "mxfp4"]
AttentionBackend = Literal["ck", "triton", "aiter"]
AttnImplementation = Literal["flash_attention_2", "sdpa", "eager"]
DataSource = Literal["hf", "local", "lighthouse", "mindx_dreams"]
QuantScheme = Literal["quark_fp8", "quark_mxfp4", "gptq_rocm", "none"]
ServeBackend = Literal["vllm-rocm", "sglang"]
ReasoningParser = Literal["deepseek_r1", "qwen3", "none"]
ToolCallParser = Literal["hermes", "qwen3_coder", "none"]
X402Network = Literal["algorand", "base", "base-sepolia"]
ScheduleType = Literal["cosine", "linear", "constant", "wsd"]
OptimizerName = Literal["adamw_torch_fused", "adamw_torch", "adamw_8bit", "lion", "adafactor"]
ReceiptIncludeKey = Literal[
"rocm_version",
"gfx_arch",
"container_digest",
"all_git_shas",
"yaml_hash",
"dataset_cids",
"eval_report",
"energy_kwh",
]
_DEFAULT_RECEIPT_INCLUDE: list[ReceiptIncludeKey] = [
"rocm_version",
"gfx_arch",
"container_digest",
"all_git_shas",
"yaml_hash",
"dataset_cids",
"eval_report",
"energy_kwh",
]
# ---- meta -------------------------------------------------------------------
class MetaCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
project: str = Field(min_length=1)
run_name: str = Field(min_length=1)
seed: int = Field(default=2048, ge=0)
license: str = Field(default="apache-2.0")
description: str = Field(default="")
# ---- hardware ---------------------------------------------------------------
class HardwareCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
name: HardwareName = "mi300x"
gfx_arch: GfxArch = "gfx942"
gpus: Literal[0, 1, 8] = Field(
default=1,
description=(
"0 = CPU lane (mindX self-training, smoke runs). "
"1 or 8 = MI300X. 2/4 rejected: xGMI bandwidth is asymmetric."
),
)
# MI300X is 192 GB HBM3; consumer Radeon/RTX cards are 8-24 GB, so the floor
# is low enough for the `trl_local` lane to declare real VRAM honestly.
expected_hbm_gb: int = Field(default=192, ge=8)
# ---- autotune ---------------------------------------------------------------
class AutotuneCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
enabled: bool = True
plan_path: Path = Field(default=Path("./out/mindxtrain.tuned.yaml"))
budget_seconds: int = Field(default=60, ge=10, le=600)
policy: Literal["aot_only"] = Field(
default="aot_only",
description="AOT-only — JIT autotune is forbidden in production.",
)
# ---- model ------------------------------------------------------------------
class ModelCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
name: str = Field(description="HF Hub model ID, e.g. Qwen/Qwen3-8B")
revision: str | None = None
attn_implementation: AttnImplementation = "flash_attention_2"
torch_dtype: DType = "bfloat16"
trust_remote_code: bool = False
# ---- data -------------------------------------------------------------------
class MinHashCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
threshold: float = Field(default=0.85, ge=0.0, le=1.0)
class SemDedupCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
threshold: float = Field(default=0.95, ge=0.0, le=1.0)
model: str = "sentence-transformers/all-MiniLM-L6-v2"
class DedupeCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
minhash: MinHashCfg | None = None
semdedup: SemDedupCfg | None = None
class ShardCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
num_shards: int = Field(default=1, ge=1)
class DataCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
source: DataSource = "hf"
hf_id: str = Field(
default="",
description="HF dataset ID (e.g. tatsu-lab/alpaca). Required when source='hf'.",
)
path: Path | None = Field(
default=None,
description=(
"Filesystem root. Required when source='local' or 'mindx_dreams'. "
"For mindx_dreams: the mindX `data/memory` directory."
),
)
split: str = "train"
streaming: bool = True
max_samples: int | None = Field(default=None, ge=1)
seq_len: int = Field(default=4096, ge=64, le=1_048_576)
packing: bool = True
dedupe: DedupeCfg = Field(default_factory=DedupeCfg)
shard: ShardCfg = Field(default_factory=ShardCfg)
include_evolutions: bool = Field(
default=False,
description=(
"When source='mindx_dreams', also pull *_evolutions.jsonl "
"proposals alongside *_training.jsonl consolidation rows. "
"max_samples caps the combined total."
),
)
eval_split: float | None = Field(
default=None,
ge=0.05,
le=0.5,
description=(
"Fraction of the tokenised dataset to hold out for validation. "
"When set, the trl_cpu lane passes the held-out slice as "
"`eval_dataset` to SFTTrainer and emits eval_loss every "
"max_steps//4 steps so the Coach loss chart can plot a "
"second series. None disables eval entirely (default — keeps "
"the legacy single-loss behaviour)."
),
)
@model_validator(mode="after")
def _check_source_inputs(self) -> DataCfg:
if self.source == "hf" and not self.hf_id:
msg = "data.hf_id is required when data.source='hf'"
raise ValueError(msg)
if self.source in {"local", "mindx_dreams"} and self.path is None:
msg = f"data.path is required when data.source='{self.source}'"
raise ValueError(msg)
return self
# ---- train (discriminated method) ------------------------------------------
class _MethodBase(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
class FullMethod(_MethodBase):
kind: Literal["full"] = "full"
class LoraMethod(_MethodBase):
kind: Literal["lora"] = "lora"
r: int = Field(default=16, ge=1, le=512)
alpha: int = Field(default=32, ge=1, le=1024)
dropout: float = Field(default=0.0, ge=0.0, le=1.0)
target_modules: list[str] = Field(
default_factory=lambda: [
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
)
class QLoraMethod(_MethodBase):
kind: Literal["qlora"] = "qlora"
r: int = Field(default=16, ge=1, le=512)
alpha: int = Field(default=32, ge=1, le=1024)
dropout: float = Field(default=0.0, ge=0.0, le=1.0)
quant_bits: Literal[4, 8] = 4
target_modules: list[str] = Field(
default_factory=lambda: ["q_proj", "k_proj", "v_proj", "o_proj"],
)
class DpoMethod(_MethodBase):
kind: Literal["dpo"] = "dpo"
beta: float = Field(default=0.1, gt=0.0)
class OrpoMethod(_MethodBase):
kind: Literal["orpo"] = "orpo"
beta: float = Field(default=0.1, gt=0.0)
class GrpoMethod(_MethodBase):
kind: Literal["grpo"] = "grpo"
num_generations: int = Field(default=4, ge=2)
kl_coef: float = Field(default=0.04, ge=0.0)
class GspoMethod(_MethodBase):
"""Qwen team's preferred RL algorithm for hybrid + sparse MoE stability (Qwen3-Next/3.5/3.6)."""
kind: Literal["gspo"] = "gspo"
num_generations: int = Field(default=4, ge=2)
class KtoMethod(_MethodBase):
kind: Literal["kto"] = "kto"
beta: float = Field(default=0.1, gt=0.0)
class CptMethod(_MethodBase):
kind: Literal["cpt"] = "cpt"
TrainMethod = Annotated[
FullMethod | LoraMethod | QLoraMethod | DpoMethod | OrpoMethod
| GrpoMethod | GspoMethod | KtoMethod | CptMethod,
Discriminator("kind"),
]
class OptimizerCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
name: OptimizerName = "adamw_torch_fused"
lr: float = Field(default=1e-4, gt=0.0)
betas: tuple[float, float] = (0.9, 0.95)
weight_decay: float = Field(default=0.1, ge=0.0)
grad_clip: float = Field(default=1.0, ge=0.0)
class ScheduleCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
type: ScheduleType = "cosine"
warmup_ratio: float = Field(default=0.03, ge=0.0, le=1.0)
epochs: int = Field(default=3, ge=1, le=100)
class BatchCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
per_device: int = Field(default=8, ge=1)
grad_accum: int = Field(default=4, ge=1)
class FlashAttentionCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
backend: AttentionBackend = "ck"
class FsdpCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
enabled: bool = False
auto_wrap: bool = True
class CPUThrottleCfg(BaseModel):
"""CPU throttle for the `trl_cpu` lane — keeps the dev laptop usable.
`percent` is the fraction of available cores to use during training.
Resolved to an integer thread count via `resolve_thread_count`; thread
counts under 1 are clamped to 1 so training never deadlocks on a zero
thread pool.
`nice_level` shifts the training process's scheduler priority — useful
on a single-user laptop where you want to keep the UI responsive while
training runs in the background. 0 = default, 10 = visibly de-
prioritized but still progresses.
`omp_proc_bind` enables Ryzen-aware OpenMP affinity (`OMP_PROC_BIND=close`,
`OMP_PLACES=cores`). Helps on CCX-topology CPUs by keeping threads on
the same chiplet — reduces inter-core latency for the small matmuls
that dominate CPU training.
"""
model_config = ConfigDict(extra="forbid", frozen=True)
percent: int = Field(
default=100,
ge=1,
le=100,
description=(
"Percent of available cores to use during CPU training. "
"100 = full host (default; matches the prior behaviour). "
"50 = half the cores. 1 = single-thread (slowest)."
),
)
nice_level: int = Field(
default=0,
ge=-20,
le=19,
description=(
"POSIX nice level for the training process. 0 = default. "
"Positive values de-prioritise the trainer so a desktop UI "
"stays responsive during training. Requires CAP_SYS_NICE for "
"negative values (root) — schema accepts but `os.nice` will "
"raise if unauthorised."
),
)
omp_proc_bind: bool = Field(
default=True,
description=(
"When True, sets OMP_PROC_BIND=close + OMP_PLACES=cores so "
"OpenMP threads bind to the same CCX chiplet on Ryzen / EPYC. "
"Improves cache locality for the small matmuls that dominate "
"CPU training; safe to leave on for non-AMD CPUs too."
),
)
def resolve_thread_count(percent: int, total_cores: int) -> int:
"""Map a percentage [1, 100] to an integer thread count.
Floor at 1 — training can never run on zero threads. Ceiling at
`total_cores` (passing percent=100 on a 4-core box returns 4, never
5). Used by the trl_cpu backend to size torch / OpenMP / BLAS thread
pools uniformly.
"""
if total_cores < 1:
msg = f"total_cores must be ≥ 1; got {total_cores}"
raise ValueError(msg)
if not 1 <= percent <= 100:
msg = f"percent must be in [1, 100]; got {percent}"
raise ValueError(msg)
threads = (total_cores * percent) // 100
return max(1, min(total_cores, threads))
class TrainCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
backend: TrainingBackend = "axolotl"
method: TrainMethod = Field(default_factory=lambda: LoraMethod())
optimizer: OptimizerCfg = Field(default_factory=OptimizerCfg)
schedule: ScheduleCfg = Field(default_factory=ScheduleCfg)
batch: BatchCfg = Field(default_factory=BatchCfg)
precision: DType = "bfloat16"
gradient_checkpointing: bool = True
flash_attention: FlashAttentionCfg = Field(default_factory=FlashAttentionCfg)
fsdp: FsdpCfg = Field(default_factory=FsdpCfg)
cpu_throttle: CPUThrottleCfg = Field(default_factory=CPUThrottleCfg)
logging_steps: int = Field(
default=1,
ge=1,
le=10_000,
description=(
"How often (in optimizer steps) HF Trainer should call its "
"on_log hooks. Default 1 — every step logs, giving the Coach "
"loss chart a per-step trajectory and trainer_state.json a "
"full log_history. The trl_cpu backend floors this at "
"max(1, min(logging_steps, max_steps // 4)) so short runs "
"still get at least 4 data points even if the YAML sets a "
"higher cadence."
),
)
env: dict[str, str] = Field(
default_factory=lambda: {
"HSA_NO_SCRATCH_RECLAIM": "1",
"NVTE_CK_USES_BWD_V3": "1",
"NVTE_CK_IS_V3_ATOMIC_FP32": "1",
"PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32": "1",
"NCCL_MIN_NCHANNELS": "112",
"HIP_FORCE_DEV_KERNARG": "1",
"PYTORCH_ROCM_ARCH": "gfx942",
},
)
# ---- eval -------------------------------------------------------------------
class EvalHarnessCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
tasks: list[str] = Field(default_factory=lambda: ["mmlu", "gsm8k", "ifeval", "humaneval"])
fewshot: int = Field(default=5, ge=0)
class EvalRegressionCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
baseline: str = Field(default="", description="HF model ID of the base model for regression check")
threshold_pct: float = Field(
default=-1.0,
description="fail if any task drops more than threshold_pct (negative = allowed drop)",
)
class EvalCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
harness: EvalHarnessCfg = Field(default_factory=EvalHarnessCfg)
regression: EvalRegressionCfg = Field(default_factory=EvalRegressionCfg)
# ---- quantize ---------------------------------------------------------------
class QuantizeCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
enabled: bool = True
scheme: QuantScheme = "quark_fp8"
ptpc: bool = Field(
default=True,
description="Per-tensor-per-channel FP8 GEMM (15-30% faster than BlockScale on MI300X).",
)
# ---- serve ------------------------------------------------------------------
class ServeCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
backend: ServeBackend = "vllm-rocm"
reasoning_parser: ReasoningParser = "qwen3"
tool_call_parser: ToolCallParser = "hermes"
tensor_parallel: int = Field(default=1, ge=1)
max_model_len: int = Field(default=8192, ge=512)
port: int = Field(default=8000, ge=1024, le=65535)
# ---- publish ----------------------------------------------------------------
class HfPublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
repo: str = Field(description="HF Hub repo, e.g. lablab-ai-amd-developer-hackathon/mindxtrain-demo")
private: bool = False
class LighthousePublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
api_key_env: str = "LIGHTHOUSE_API_KEY"
class MindxPublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
api_url: str = "https://mindx.pythai.net/v1/agents"
register_as_capability: bool = True
class AgenticPlacePublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
api_url: str = "https://agenticplace.pythai.net/v1/listings"
chain_map_url: str = "https://agenticplace.pythai.net/allchain.html"
class BankonPublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
ens_parent: str = "bankon.eth"
subname: str = ""
class X402Cfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
network: X402Network = "algorand"
asset: str = "USDC"
receiver_via: Literal["parsec_wallet", "coinbase_facilitator"] = "parsec_wallet"
price_per_1k_tokens: float = Field(default=0.0002, ge=0.0)
class BillingPublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
x402: X402Cfg = Field(default_factory=X402Cfg)
class PublishCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
enabled: bool = True
hf: HfPublishCfg | None = None
lighthouse: LighthousePublishCfg = Field(default_factory=LighthousePublishCfg)
mindx: MindxPublishCfg = Field(default_factory=MindxPublishCfg)
agenticplace: AgenticPlacePublishCfg = Field(default_factory=AgenticPlacePublishCfg)
bankon: BankonPublishCfg = Field(default_factory=BankonPublishCfg)
billing: BillingPublishCfg = Field(default_factory=BillingPublishCfg)
# ---- receipt ----------------------------------------------------------------
class ReceiptCfg(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
output: Path = Field(default=Path("./out/receipt.json"))
include: list[ReceiptIncludeKey] = Field(
default_factory=lambda: _DEFAULT_RECEIPT_INCLUDE.copy(),
)
# ---- root -------------------------------------------------------------------
class XTrainConfig(BaseModel):
"""Canonical mindXtrain YAML — consumed by `mindxtrain` CLI verbs."""
model_config = ConfigDict(extra="forbid", frozen=True)
meta: MetaCfg
hardware: HardwareCfg = Field(default_factory=HardwareCfg)
autotune: AutotuneCfg = Field(default_factory=AutotuneCfg)
model: ModelCfg
data: DataCfg
train: TrainCfg = Field(default_factory=lambda: TrainCfg())
eval: EvalCfg = Field(default_factory=EvalCfg)
quantize: QuantizeCfg = Field(default_factory=QuantizeCfg)
serve: ServeCfg = Field(default_factory=ServeCfg)
publish: PublishCfg = Field(default_factory=PublishCfg)
receipt: ReceiptCfg = Field(default_factory=ReceiptCfg)