Overhaul model system with multi-stage training, variants, benchmarks, and eval
CI / build-and-push (push) Successful in 32s
CI / build-and-push (push) Successful in 32s
Replace the single-stage training + flat capability score with a realistic AI development pipeline: pre-training with Chinchilla scaling laws, SFT with specializations, alignment with safety/capability tradeoffs (RLHF/DPO/Constitutional), model families with distillation/fine-tuning/quantization variants, named benchmark suite with compute-costing eval jobs, and segment-specific market quality. Phases 1-6 of the model rework plan: new types, engine rewrite, save migration, training events/risk system, concurrent training, variant creation, benchmark evaluation with leaderboard, and market integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,9 +107,9 @@ export function StateInspectionTab() {
|
||||
<Stat label="Completed" value={research.completedResearch.length} />
|
||||
<Stat label="Points" value={research.researchPoints.toFixed(1)} />
|
||||
<Stat label="Active" value={research.activeResearch?.researchId ?? 'None'} />
|
||||
<Stat label="Models" value={models.trainedModels.length} />
|
||||
<Stat label="Training" value={models.activeTraining?.modelName ?? 'None'} />
|
||||
<Stat label="Deployed" value={models.trainedModels.filter(m => m.isDeployed).length} />
|
||||
<Stat label="Models" value={models.baseModels.length} />
|
||||
<Stat label="Training" value={models.activeTrainingPipelines.filter(p => p.status === 'active').length} />
|
||||
<Stat label="Deployed" value={models.baseModels.filter(m => m.isDeployed).length} />
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -111,12 +111,17 @@ function instantCompleteResearch() {
|
||||
}
|
||||
|
||||
function instantCompleteTraining() {
|
||||
const { activeTraining } = useGameStore.getState().models;
|
||||
if (!activeTraining) return;
|
||||
const { activeTrainingPipelines } = useGameStore.getState().models;
|
||||
const active = activeTrainingPipelines.find(p => p.status === 'active');
|
||||
if (!active) return;
|
||||
useGameStore.setState((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
activeTraining: { ...activeTraining, progressTicks: activeTraining.totalTicks },
|
||||
activeTrainingPipelines: s.models.activeTrainingPipelines.map(p =>
|
||||
p.id === active.id
|
||||
? { ...p, stages: { ...p.stages, pretraining: { ...p.stages.pretraining, progressTicks: p.stages.pretraining.totalTicks } } }
|
||||
: p,
|
||||
),
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -137,7 +142,7 @@ function forceEra(era: Era) {
|
||||
export function TimeCompletionTab() {
|
||||
const [tickCount, setTickCount] = useState('100');
|
||||
const activeResearch = useGameStore((s) => s.research.activeResearch);
|
||||
const activeTraining = useGameStore((s) => s.models.activeTraining);
|
||||
const activeTraining = useGameStore((s) => s.models.activeTrainingPipelines.find(p => p.status === 'active'));
|
||||
const currentEra = useGameStore((s) => s.meta.currentEra);
|
||||
|
||||
const pipelineCount = useGameStore((s) =>
|
||||
@@ -189,6 +194,7 @@ export function TimeCompletionTab() {
|
||||
</DevButton>
|
||||
<DevButton onClick={instantCompleteTraining} variant="success">
|
||||
Training {activeTraining && `(${activeTraining.modelName})`}
|
||||
|
||||
</DevButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,10 +13,8 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) {
|
||||
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
||||
const valuation = useGameStore((s) => s.economy.funding.valuation);
|
||||
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
||||
const models = useGameStore((s) => s.models.trainedModels.length);
|
||||
const bestModel = useGameStore((s) =>
|
||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
||||
);
|
||||
const models = useGameStore((s) => s.models.baseModels.length);
|
||||
const bestModel = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||
const reputation = useGameStore((s) => s.reputation.score);
|
||||
const achievements = useGameStore((s) => s.achievements.unlocked.length);
|
||||
const dataCenters = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
||||
|
||||
@@ -22,9 +22,7 @@ const ARCHETYPE_COLORS: Record<string, string> = {
|
||||
export function CompetitorsPage() {
|
||||
const rivals = useGameStore((s) => s.competitors.rivals);
|
||||
const industryBenchmark = useGameStore((s) => s.competitors.industryBenchmark);
|
||||
const playerBest = useGameStore((s) =>
|
||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
||||
);
|
||||
const playerBest = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||
const era = useGameStore((s) => s.meta.currentEra);
|
||||
const money = useGameStore((s) => s.economy.money);
|
||||
const acquireCompetitor = useGameStore((s) => s.acquireCompetitor);
|
||||
|
||||
@@ -13,8 +13,8 @@ export function DashboardPage() {
|
||||
const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick);
|
||||
const totalFlops = useGameStore((s) => s.infrastructure.totalFlops);
|
||||
const totalDCs = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
||||
const trainedModels = useGameStore((s) => s.models.trainedModels);
|
||||
const activeTraining = useGameStore((s) => s.models.activeTraining);
|
||||
const baseModels = useGameStore((s) => s.models.baseModels);
|
||||
const activePipelines = useGameStore((s) => s.models.activeTrainingPipelines);
|
||||
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
||||
const reputation = useGameStore((s) => s.reputation.score);
|
||||
const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization);
|
||||
@@ -33,13 +33,13 @@ export function DashboardPage() {
|
||||
</TutorialHint>
|
||||
)}
|
||||
|
||||
{totalDCs > 0 && trainedModels.length === 0 && !activeTraining && (
|
||||
{totalDCs > 0 && baseModels.length === 0 && activePipelines.length === 0 && (
|
||||
<TutorialHint id="train-first-model">
|
||||
You have compute available! Head to the Models tab to allocate compute for training and start your first model.
|
||||
</TutorialHint>
|
||||
)}
|
||||
|
||||
{trainedModels.length > 0 && !trainedModels.some(m => m.isDeployed) && (
|
||||
{baseModels.length > 0 && !baseModels.some(m => m.isDeployed) && (
|
||||
<TutorialHint id="deploy-model">
|
||||
Your model is trained! Deploy it from the Models tab to start serving customers and earning revenue.
|
||||
</TutorialHint>
|
||||
@@ -66,8 +66,8 @@ export function DashboardPage() {
|
||||
<StatCard
|
||||
icon={Brain}
|
||||
label="Models"
|
||||
value={trainedModels.length.toString()}
|
||||
subValue={activeTraining ? `Training: ${Math.floor((activeTraining.progressTicks / activeTraining.totalTicks) * 100)}%` : 'Idle'}
|
||||
value={baseModels.length.toString()}
|
||||
subValue={activePipelines.filter(p => p.status === 'active').length > 0 ? `Training: ${activePipelines.filter(p => p.status === 'active').length} active` : 'Idle'}
|
||||
color="text-purple-400"
|
||||
onClick={() => useGameStore.getState().setActivePage('models')}
|
||||
/>
|
||||
|
||||
@@ -26,9 +26,7 @@ export function LeaderboardPage() {
|
||||
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
||||
const era = useGameStore((s) => s.meta.currentEra);
|
||||
const tickCount = useGameStore((s) => s.meta.tickCount);
|
||||
const bestModel = useGameStore((s) =>
|
||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
||||
);
|
||||
const bestModel = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -38,10 +38,7 @@ export function MarketPage() {
|
||||
const tokensDemand = useGameStore((s) => s.compute.tokensPerSecondDemand);
|
||||
const currentEra = useGameStore((s) => s.meta.currentEra);
|
||||
const reputationScore = useGameStore((s) => s.reputation.score);
|
||||
const bestQuality = useGameStore((s) => {
|
||||
const deployed = s.models.trainedModels.filter(m => m.isDeployed);
|
||||
return deployed.length > 0 ? Math.max(...deployed.map(m => m.benchmarkScore)) / 100 : 0;
|
||||
});
|
||||
const bestQuality = useGameStore((s) => s.models.bestDeployedModelScore / 100);
|
||||
const setProductPricing = useGameStore((s) => s.setProductPricing);
|
||||
const setOverloadPolicy = useGameStore((s) => s.setOverloadPolicy);
|
||||
const pricingFeedback = useAppliedFeedback();
|
||||
|
||||
+998
-195
File diff suppressed because it is too large
Load Diff
+193
-17
@@ -6,13 +6,17 @@ import type {
|
||||
ResearchState, ModelsState, MarketState,
|
||||
CompetitorState, TalentState, DataState,
|
||||
ReputationState, AchievementState,
|
||||
Cluster, Campus, DataCenter, DCTier, RackSkuId, TrainingJob,
|
||||
Cluster, Campus, DataCenter, DCTier, RackSkuId,
|
||||
ActiveResearch, OwnedDataset, LocationId,
|
||||
DeploymentCohort, PipelineStage,
|
||||
CampusRetrofitQueue,
|
||||
CoolingType, NetworkFabric,
|
||||
FundingRoundType, OverloadPolicy,
|
||||
TrainingPipeline, ModelFamily, DataMixAllocation,
|
||||
ModelArchitecture,
|
||||
SFTSpecialization, QuantizationLevel, VariantCreationJob,
|
||||
EvalJob,
|
||||
} from '@ai-tycoon/shared';
|
||||
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
|
||||
import {
|
||||
INITIAL_SETTINGS, SAVE_VERSION,
|
||||
INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE,
|
||||
@@ -29,9 +33,15 @@ import {
|
||||
estimateNetworkSlots, maxComputeRacks,
|
||||
uuid,
|
||||
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,
|
||||
} from '@ai-tycoon/shared';
|
||||
import {
|
||||
emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary,
|
||||
BENCHMARKS,
|
||||
} from '@ai-tycoon/game-engine';
|
||||
import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
|
||||
|
||||
@@ -97,8 +107,15 @@ interface Actions {
|
||||
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => void;
|
||||
upgradeCoolingType: (dataCenterId: string, targetCooling: CoolingType) => void;
|
||||
upgradeNetworkFabric: (dataCenterId: string, targetFabric: NetworkFabric) => void;
|
||||
startTraining: (job: Omit<TrainingJob, 'progressTicks'>) => 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;
|
||||
createQuantization: (baseModelId: string, level: QuantizationLevel, variantName: string) => void;
|
||||
startEvaluation: (modelId: string, benchmarkIds: string[]) => void;
|
||||
deployModel: (modelId: string) => void;
|
||||
deployVariant: (familyId: string, variantId: string) => void;
|
||||
setProductPricing: (productLineId: string, field: string, value: number) => void;
|
||||
toggleProductLine: (productLineId: string) => void;
|
||||
startResearch: (research: ActiveResearch) => void;
|
||||
@@ -107,7 +124,6 @@ interface Actions {
|
||||
raiseFunding: (roundType: FundingRoundType) => void;
|
||||
openSourceModel: (modelId: string) => void;
|
||||
setOverloadPolicy: (policy: Partial<OverloadPolicy>) => void;
|
||||
setModelTuning: (modelId: string, tuning: Partial<ModelTuning>) => void;
|
||||
acquireCompetitor: (competitorId: string) => void;
|
||||
updateState: (partial: Partial<GameState>) => void;
|
||||
}
|
||||
@@ -873,17 +889,175 @@ export const useGameStore = create<Store>()(
|
||||
|
||||
// --- Non-infrastructure actions (unchanged) ---
|
||||
|
||||
startTraining: (job) => set((s) => ({
|
||||
startTrainingPipeline: (config) => 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;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
const pipeline: TrainingPipeline = {
|
||||
id: pipelineId,
|
||||
familyId,
|
||||
modelName: config.modelName,
|
||||
architecture: config.architecture,
|
||||
dataMix: config.dataMix,
|
||||
currentStage: 'pretraining',
|
||||
stages: {
|
||||
pretraining: {
|
||||
targetTokens: config.targetTokens,
|
||||
processedTokens: 0,
|
||||
computeAllocated: 0,
|
||||
progressTicks: 0,
|
||||
totalTicks: config.totalTicks,
|
||||
lossValue: 10,
|
||||
chinchillaRatio: config.targetTokens / (config.architecture.totalParameters * 1e9),
|
||||
isComplete: false,
|
||||
},
|
||||
sft: null,
|
||||
alignment: null,
|
||||
},
|
||||
status: 'active',
|
||||
allocatedComputeFraction: config.allocatedComputeFraction,
|
||||
events: [],
|
||||
startedAtTick: s.meta.tickCount,
|
||||
};
|
||||
|
||||
return {
|
||||
models: {
|
||||
...s.models,
|
||||
families: [...s.models.families, family],
|
||||
activeTrainingPipelines: [...s.models.activeTrainingPipelines, pipeline],
|
||||
},
|
||||
};
|
||||
}),
|
||||
|
||||
configureSFT: (pipelineId, specializations) => set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
activeTraining: { ...job, progressTicks: 0 },
|
||||
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,
|
||||
),
|
||||
},
|
||||
})),
|
||||
|
||||
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,
|
||||
),
|
||||
},
|
||||
})),
|
||||
|
||||
createDistillation: (baseModelId, targetParameters, variantName) => set((s) => {
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return s;
|
||||
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] } };
|
||||
}),
|
||||
|
||||
createFineTune: (baseModelId, specialization, variantName) => set((s) => {
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return s;
|
||||
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] } };
|
||||
}),
|
||||
|
||||
createQuantization: (baseModelId, level, variantName) => set((s) => {
|
||||
const base = s.models.baseModels.find(m => m.id === baseModelId);
|
||||
if (!base) return s;
|
||||
const job: VariantCreationJob = {
|
||||
id: uuid(),
|
||||
familyId: base.familyId,
|
||||
baseModelId,
|
||||
jobType: 'quantization',
|
||||
config: { level, variantName },
|
||||
progressTicks: 0,
|
||||
totalTicks: QUANTIZATION_TICKS,
|
||||
allocatedComputeFraction: 0,
|
||||
status: 'active',
|
||||
};
|
||||
return { models: { ...s.models, variantJobs: [...s.models.variantJobs, job] } };
|
||||
}),
|
||||
|
||||
startEvaluation: (modelId, benchmarkIds) => set((s) => {
|
||||
const benchmarks = BENCHMARKS.filter(b => benchmarkIds.includes(b.id));
|
||||
if (benchmarks.length === 0) return s;
|
||||
const totalTicks = benchmarks.reduce((sum, b) => sum + b.ticksToRun, 0);
|
||||
const computeCost = benchmarks.reduce((sum, b) => sum + b.computeCost, 0);
|
||||
const job: EvalJob = {
|
||||
id: uuid(),
|
||||
modelId,
|
||||
benchmarkIds,
|
||||
progressTicks: 0,
|
||||
totalTicks,
|
||||
computeAllocated: computeCost,
|
||||
status: 'active',
|
||||
results: [],
|
||||
};
|
||||
return { models: { ...s.models, evalJobs: [...s.models.evalJobs, job] } };
|
||||
}),
|
||||
|
||||
deployModel: (modelId) => set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
trainedModels: s.models.trainedModels.map(m =>
|
||||
baseModels: s.models.baseModels.map(m =>
|
||||
m.id === modelId ? { ...m, isDeployed: true } : m,
|
||||
),
|
||||
productLines: s.models.productLines.map(pl => ({
|
||||
@@ -892,6 +1066,17 @@ export const useGameStore = create<Store>()(
|
||||
},
|
||||
})),
|
||||
|
||||
deployVariant: (familyId, variantId) => set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
families: s.models.families.map(f =>
|
||||
f.id === familyId
|
||||
? { ...f, variants: f.variants.map(v => v.id === variantId ? { ...v, isDeployed: true } : v) }
|
||||
: f,
|
||||
),
|
||||
},
|
||||
})),
|
||||
|
||||
setProductPricing: (productLineId, field, value) => set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
@@ -996,15 +1181,6 @@ export const useGameStore = create<Store>()(
|
||||
},
|
||||
})),
|
||||
|
||||
setModelTuning: (modelId, tuning) => set((s) => ({
|
||||
models: {
|
||||
...s.models,
|
||||
trainedModels: s.models.trainedModels.map(m =>
|
||||
m.id === modelId ? { ...m, tuning: { ...m.tuning, ...tuning } } : m,
|
||||
),
|
||||
},
|
||||
})),
|
||||
|
||||
acquireCompetitor: (competitorId) => set((s) => {
|
||||
const rival = s.competitors.rivals.find(r => r.id === competitorId);
|
||||
if (!rival || rival.status === 'acquired') return s;
|
||||
@@ -1058,7 +1234,7 @@ export const useGameStore = create<Store>()(
|
||||
notifications: [{
|
||||
id: uuid(),
|
||||
title: 'Save Reset',
|
||||
message: 'Your save was reset due to a major rack system overhaul — 20 SKUs with training/inference specialization, VRAM, cooling tech, interconnects, and AMD/ASIC vendors!',
|
||||
message: 'Your save was reset due to a major model system overhaul — multi-stage training pipelines, model families with variants, benchmarks, and architecture choices!',
|
||||
type: 'info' as const,
|
||||
tick: 0,
|
||||
read: false,
|
||||
|
||||
Reference in New Issue
Block a user