Redesign model lifecycle: upfront SFT/alignment, multi-size families, point releases, quantization-only variants
CI / build-and-push (push) Successful in 45s
CI / build-and-push (push) Successful in 45s
Training pipeline now requires SFT specializations and alignment method configured at start — no more mid-training configuration step. Model families support multiple size tiers (Nano/Small/Medium/Large/Flagship) trained independently, mimicking real AI company model families. Point releases iterate on deployed models with 40% training time and 8% capability gain. Distillation and fine-tuning variants removed — players train smaller size tiers or configure SFT during initial training instead. Only quantization remains as a variant type. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+94
-120
@@ -13,7 +13,7 @@ import type {
|
||||
CoolingType, NetworkFabric,
|
||||
FundingRoundType, OverloadPolicy,
|
||||
TrainingPipeline, ModelFamily, DataMixAllocation,
|
||||
ModelArchitecture,
|
||||
ModelArchitecture, AlignmentMethod, SizeTier,
|
||||
SFTSpecialization, QuantizationLevel, VariantCreationJob,
|
||||
EvalJob,
|
||||
ConsumerTierId, ApiTierId,
|
||||
@@ -36,9 +36,10 @@ import {
|
||||
COOLING_TYPE_CONFIGS, COOLING_ORDER, NETWORK_FABRIC_CONFIGS, FABRIC_ORDER,
|
||||
DEFAULT_DATA_MIX,
|
||||
MAX_CONCURRENT_TRAINING,
|
||||
DISTILLATION_TIME_FRACTION, DISTILLATION_COMPUTE_FRACTION,
|
||||
FINETUNE_TIME_FRACTION, FINETUNE_COMPUTE_FRACTION,
|
||||
QUANTIZATION_TICKS,
|
||||
SFT_TIME_FRACTION, ALIGNMENT_TIME_FRACTION,
|
||||
SIZE_TIER_MAP, SIZE_TIER_LABELS,
|
||||
POINT_RELEASE_TIME_FRACTION, POINT_RELEASE_MAX_VERSION,
|
||||
} from '@ai-tycoon/shared';
|
||||
import {
|
||||
emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary,
|
||||
@@ -115,11 +116,21 @@ interface Actions {
|
||||
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => void;
|
||||
upgradeCoolingType: (dataCenterId: string, targetCooling: CoolingType) => void;
|
||||
upgradeNetworkFabric: (dataCenterId: string, targetFabric: NetworkFabric) => void;
|
||||
startTrainingPipeline: (config: { modelName: string; architecture: ModelArchitecture; dataMix: DataMixAllocation; allocatedComputeFraction: number; targetTokens: number; totalTicks: number }) => void;
|
||||
configureSFT: (pipelineId: string, specializations: import('@ai-tycoon/shared').SFTSpecialization[]) => void;
|
||||
configureAlignment: (pipelineId: string, method: import('@ai-tycoon/shared').AlignmentMethod, safetyWeight: number) => void;
|
||||
createDistillation: (baseModelId: string, targetParameters: number, variantName: string) => void;
|
||||
createFineTune: (baseModelId: string, specialization: SFTSpecialization, variantName: string) => void;
|
||||
startTrainingPipeline: (config: {
|
||||
familyId?: string;
|
||||
familyName?: string;
|
||||
architecture: ModelArchitecture;
|
||||
dataMix: DataMixAllocation;
|
||||
allocatedComputeFraction: number;
|
||||
targetTokens: number;
|
||||
totalTicks: number;
|
||||
sftSpecializations: SFTSpecialization[];
|
||||
alignmentMethod: AlignmentMethod;
|
||||
alignmentSafetyWeight: number;
|
||||
isPointRelease?: boolean;
|
||||
sourceModelId?: string;
|
||||
}) => void;
|
||||
startPointRelease: (baseModelId: string) => void;
|
||||
createQuantization: (baseModelId: string, level: QuantizationLevel, variantName: string) => void;
|
||||
startEvaluation: (modelId: string, benchmarkIds: string[]) => void;
|
||||
deployModel: (modelId: string) => void;
|
||||
@@ -917,29 +928,52 @@ export const useGameStore = create<Store>()(
|
||||
|
||||
startTrainingPipeline: (config) => {
|
||||
let created = false;
|
||||
let toastName = '';
|
||||
set((s) => {
|
||||
const activeCount = s.models.activeTrainingPipelines.filter(p => p.status === 'active' || p.status === 'stalled').length;
|
||||
const maxSlots = MAX_CONCURRENT_TRAINING[s.meta.currentEra] ?? 1;
|
||||
if (activeCount >= maxSlots) return s;
|
||||
|
||||
created = true;
|
||||
const familyId = uuid();
|
||||
const pipelineId = uuid();
|
||||
const generation = s.models.families.length + 1;
|
||||
|
||||
const family: ModelFamily = {
|
||||
id: familyId,
|
||||
name: config.modelName,
|
||||
generation,
|
||||
baseModelId: null,
|
||||
variants: [],
|
||||
createdAtTick: s.meta.tickCount,
|
||||
};
|
||||
let familyId: string;
|
||||
let updatedFamilies = [...s.models.families];
|
||||
|
||||
if (config.familyId) {
|
||||
familyId = config.familyId;
|
||||
} else {
|
||||
familyId = uuid();
|
||||
const generation = s.models.families.length + 1;
|
||||
const family: ModelFamily = {
|
||||
id: familyId,
|
||||
name: config.familyName ?? 'Model',
|
||||
generation,
|
||||
baseModelIds: [],
|
||||
variants: [],
|
||||
createdAtTick: s.meta.tickCount,
|
||||
};
|
||||
updatedFamilies = [...updatedFamilies, family];
|
||||
}
|
||||
|
||||
const sizeTier: SizeTier = SIZE_TIER_MAP[config.architecture.totalParameters] ?? 'small';
|
||||
const familyName = config.familyName ?? updatedFamilies.find(f => f.id === familyId)?.name ?? 'Model';
|
||||
const version = config.isPointRelease && config.sourceModelId
|
||||
? (() => {
|
||||
const src = s.models.baseModels.find(m => m.id === config.sourceModelId);
|
||||
return src ? Math.round((src.version + 0.1) * 10) / 10 : 1.0;
|
||||
})()
|
||||
: 1.0;
|
||||
const modelName = `${familyName} ${SIZE_TIER_LABELS[sizeTier]} v${version.toFixed(1)}`;
|
||||
toastName = modelName;
|
||||
|
||||
const baseTotalTicks = config.isPointRelease
|
||||
? Math.ceil(config.totalTicks * POINT_RELEASE_TIME_FRACTION)
|
||||
: config.totalTicks;
|
||||
|
||||
const pipeline: TrainingPipeline = {
|
||||
id: pipelineId,
|
||||
id: uuid(),
|
||||
familyId,
|
||||
modelName: config.modelName,
|
||||
modelName,
|
||||
architecture: config.architecture,
|
||||
dataMix: config.dataMix,
|
||||
currentStage: 'pretraining',
|
||||
@@ -949,130 +983,70 @@ export const useGameStore = create<Store>()(
|
||||
processedTokens: 0,
|
||||
computeAllocated: 0,
|
||||
progressTicks: 0,
|
||||
totalTicks: config.totalTicks,
|
||||
totalTicks: baseTotalTicks,
|
||||
lossValue: 10,
|
||||
chinchillaRatio: config.targetTokens / (config.architecture.totalParameters * 1e9),
|
||||
isComplete: false,
|
||||
},
|
||||
sft: null,
|
||||
alignment: null,
|
||||
sft: {
|
||||
specializations: config.sftSpecializations,
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(baseTotalTicks * SFT_TIME_FRACTION),
|
||||
isComplete: false,
|
||||
},
|
||||
alignment: {
|
||||
method: config.alignmentMethod,
|
||||
safetyWeight: config.alignmentSafetyWeight,
|
||||
helpfulnessWeight: 1 - config.alignmentSafetyWeight,
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(baseTotalTicks * ALIGNMENT_TIME_FRACTION),
|
||||
isComplete: false,
|
||||
},
|
||||
},
|
||||
status: 'active',
|
||||
allocatedComputeFraction: config.allocatedComputeFraction,
|
||||
events: [],
|
||||
startedAtTick: s.meta.tickCount,
|
||||
sizeTier,
|
||||
isPointRelease: config.isPointRelease ?? false,
|
||||
sourceModelId: config.sourceModelId ?? null,
|
||||
};
|
||||
|
||||
return {
|
||||
models: {
|
||||
...s.models,
|
||||
families: [...s.models.families, family],
|
||||
families: updatedFamilies,
|
||||
activeTrainingPipelines: [...s.models.activeTrainingPipelines, pipeline],
|
||||
},
|
||||
};
|
||||
});
|
||||
if (created) {
|
||||
get().addNotification({ title: 'Training Started', message: `${config.modelName} pre-training has begun.`, type: 'info', tick: get().meta.tickCount });
|
||||
get().addNotification({ title: 'Training Started', message: `${toastName} training has begun.`, type: 'info', tick: get().meta.tickCount });
|
||||
set({ modelsTab: 'overview' as ModelsTab });
|
||||
}
|
||||
},
|
||||
|
||||
configureSFT: (pipelineId, specializations) => {
|
||||
set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
activeTrainingPipelines: s.models.activeTrainingPipelines.map(p =>
|
||||
p.id === pipelineId ? {
|
||||
...p,
|
||||
stages: {
|
||||
...p.stages,
|
||||
sft: {
|
||||
specializations,
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(p.stages.pretraining.totalTicks * 0.10),
|
||||
isComplete: false,
|
||||
},
|
||||
},
|
||||
} : p,
|
||||
),
|
||||
},
|
||||
}));
|
||||
get().addNotification({ title: 'SFT Configured', message: `${specializations.join(', ')} specializations enabled.`, type: 'success', tick: get().meta.tickCount });
|
||||
},
|
||||
startPointRelease: (baseModelId) => {
|
||||
const s = get();
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return;
|
||||
if (base.version >= POINT_RELEASE_MAX_VERSION) return;
|
||||
const family = s.models.families.find(f => f.id === base.familyId);
|
||||
if (!family) return;
|
||||
|
||||
configureAlignment: (pipelineId, method, safetyWeight) => {
|
||||
set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
activeTrainingPipelines: s.models.activeTrainingPipelines.map(p =>
|
||||
p.id === pipelineId ? {
|
||||
...p,
|
||||
stages: {
|
||||
...p.stages,
|
||||
alignment: {
|
||||
method,
|
||||
safetyWeight,
|
||||
helpfulnessWeight: 1 - safetyWeight,
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(p.stages.pretraining.totalTicks * 0.08),
|
||||
isComplete: false,
|
||||
},
|
||||
},
|
||||
} : p,
|
||||
),
|
||||
},
|
||||
}));
|
||||
get().addNotification({ title: 'Alignment Configured', message: `${method.toUpperCase()} alignment enabled.`, type: 'success', tick: get().meta.tickCount });
|
||||
},
|
||||
|
||||
createDistillation: (baseModelId, targetParameters, variantName) => {
|
||||
let created = false;
|
||||
set((s) => {
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return s;
|
||||
created = true;
|
||||
const job: VariantCreationJob = {
|
||||
id: uuid(),
|
||||
familyId: base.familyId,
|
||||
baseModelId,
|
||||
jobType: 'distillation',
|
||||
config: { targetParameters, targetArchitecture: base.architecture.type, variantName },
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(base.trainingCostTotal > 0 ? DISTILLATION_TIME_FRACTION * 120 : 30),
|
||||
allocatedComputeFraction: DISTILLATION_COMPUTE_FRACTION,
|
||||
status: 'active',
|
||||
};
|
||||
return { models: { ...s.models, variantJobs: [...s.models.variantJobs, job] } };
|
||||
get().startTrainingPipeline({
|
||||
familyId: base.familyId,
|
||||
architecture: base.architecture,
|
||||
dataMix: base.dataMix,
|
||||
allocatedComputeFraction: 1.0,
|
||||
targetTokens: base.architecture.totalParameters * 20e9,
|
||||
totalTicks: Math.ceil(base.architecture.totalParameters * 2 + 60),
|
||||
sftSpecializations: base.sftSpecializations,
|
||||
alignmentMethod: base.alignmentMethod ?? 'rlhf',
|
||||
alignmentSafetyWeight: 0.5,
|
||||
isPointRelease: true,
|
||||
sourceModelId: baseModelId,
|
||||
});
|
||||
if (created) {
|
||||
get().addNotification({ title: 'Distillation Started', message: `${variantName} distillation in progress.`, type: 'info', tick: get().meta.tickCount });
|
||||
set({ modelsTab: 'overview' as ModelsTab });
|
||||
}
|
||||
},
|
||||
|
||||
createFineTune: (baseModelId, specialization, variantName) => {
|
||||
let created = false;
|
||||
set((s) => {
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return s;
|
||||
created = true;
|
||||
const job: VariantCreationJob = {
|
||||
id: uuid(),
|
||||
familyId: base.familyId,
|
||||
baseModelId,
|
||||
jobType: 'fine-tuning',
|
||||
config: { specialization, datasetIds: [], variantName },
|
||||
progressTicks: 0,
|
||||
totalTicks: Math.ceil(FINETUNE_TIME_FRACTION * 120),
|
||||
allocatedComputeFraction: FINETUNE_COMPUTE_FRACTION,
|
||||
status: 'active',
|
||||
};
|
||||
return { models: { ...s.models, variantJobs: [...s.models.variantJobs, job] } };
|
||||
});
|
||||
if (created) {
|
||||
get().addNotification({ title: 'Fine-Tuning Started', message: `${variantName} fine-tuning in progress.`, type: 'info', tick: get().meta.tickCount });
|
||||
set({ modelsTab: 'overview' as ModelsTab });
|
||||
}
|
||||
},
|
||||
|
||||
createQuantization: (baseModelId, level, variantName) => {
|
||||
|
||||
Reference in New Issue
Block a user