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="Completed" value={research.completedResearch.length} />
|
||||||
<Stat label="Points" value={research.researchPoints.toFixed(1)} />
|
<Stat label="Points" value={research.researchPoints.toFixed(1)} />
|
||||||
<Stat label="Active" value={research.activeResearch?.researchId ?? 'None'} />
|
<Stat label="Active" value={research.activeResearch?.researchId ?? 'None'} />
|
||||||
<Stat label="Models" value={models.trainedModels.length} />
|
<Stat label="Models" value={models.baseModels.length} />
|
||||||
<Stat label="Training" value={models.activeTraining?.modelName ?? 'None'} />
|
<Stat label="Training" value={models.activeTrainingPipelines.filter(p => p.status === 'active').length} />
|
||||||
<Stat label="Deployed" value={models.trainedModels.filter(m => m.isDeployed).length} />
|
<Stat label="Deployed" value={models.baseModels.filter(m => m.isDeployed).length} />
|
||||||
</Section>
|
</Section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -111,12 +111,17 @@ function instantCompleteResearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function instantCompleteTraining() {
|
function instantCompleteTraining() {
|
||||||
const { activeTraining } = useGameStore.getState().models;
|
const { activeTrainingPipelines } = useGameStore.getState().models;
|
||||||
if (!activeTraining) return;
|
const active = activeTrainingPipelines.find(p => p.status === 'active');
|
||||||
|
if (!active) return;
|
||||||
useGameStore.setState((s) => ({
|
useGameStore.setState((s) => ({
|
||||||
models: {
|
models: {
|
||||||
...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() {
|
export function TimeCompletionTab() {
|
||||||
const [tickCount, setTickCount] = useState('100');
|
const [tickCount, setTickCount] = useState('100');
|
||||||
const activeResearch = useGameStore((s) => s.research.activeResearch);
|
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 currentEra = useGameStore((s) => s.meta.currentEra);
|
||||||
|
|
||||||
const pipelineCount = useGameStore((s) =>
|
const pipelineCount = useGameStore((s) =>
|
||||||
@@ -189,6 +194,7 @@ export function TimeCompletionTab() {
|
|||||||
</DevButton>
|
</DevButton>
|
||||||
<DevButton onClick={instantCompleteTraining} variant="success">
|
<DevButton onClick={instantCompleteTraining} variant="success">
|
||||||
Training {activeTraining && `(${activeTraining.modelName})`}
|
Training {activeTraining && `(${activeTraining.modelName})`}
|
||||||
|
|
||||||
</DevButton>
|
</DevButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,10 +13,8 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) {
|
|||||||
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
||||||
const valuation = useGameStore((s) => s.economy.funding.valuation);
|
const valuation = useGameStore((s) => s.economy.funding.valuation);
|
||||||
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
||||||
const models = useGameStore((s) => s.models.trainedModels.length);
|
const models = useGameStore((s) => s.models.baseModels.length);
|
||||||
const bestModel = useGameStore((s) =>
|
const bestModel = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
|
||||||
);
|
|
||||||
const reputation = useGameStore((s) => s.reputation.score);
|
const reputation = useGameStore((s) => s.reputation.score);
|
||||||
const achievements = useGameStore((s) => s.achievements.unlocked.length);
|
const achievements = useGameStore((s) => s.achievements.unlocked.length);
|
||||||
const dataCenters = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
const dataCenters = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ const ARCHETYPE_COLORS: Record<string, string> = {
|
|||||||
export function CompetitorsPage() {
|
export function CompetitorsPage() {
|
||||||
const rivals = useGameStore((s) => s.competitors.rivals);
|
const rivals = useGameStore((s) => s.competitors.rivals);
|
||||||
const industryBenchmark = useGameStore((s) => s.competitors.industryBenchmark);
|
const industryBenchmark = useGameStore((s) => s.competitors.industryBenchmark);
|
||||||
const playerBest = useGameStore((s) =>
|
const playerBest = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
|
||||||
);
|
|
||||||
const era = useGameStore((s) => s.meta.currentEra);
|
const era = useGameStore((s) => s.meta.currentEra);
|
||||||
const money = useGameStore((s) => s.economy.money);
|
const money = useGameStore((s) => s.economy.money);
|
||||||
const acquireCompetitor = useGameStore((s) => s.acquireCompetitor);
|
const acquireCompetitor = useGameStore((s) => s.acquireCompetitor);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export function DashboardPage() {
|
|||||||
const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick);
|
const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick);
|
||||||
const totalFlops = useGameStore((s) => s.infrastructure.totalFlops);
|
const totalFlops = useGameStore((s) => s.infrastructure.totalFlops);
|
||||||
const totalDCs = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
const totalDCs = useGameStore((s) => s.infrastructure.totalDataCenterCount);
|
||||||
const trainedModels = useGameStore((s) => s.models.trainedModels);
|
const baseModels = useGameStore((s) => s.models.baseModels);
|
||||||
const activeTraining = useGameStore((s) => s.models.activeTraining);
|
const activePipelines = useGameStore((s) => s.models.activeTrainingPipelines);
|
||||||
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
||||||
const reputation = useGameStore((s) => s.reputation.score);
|
const reputation = useGameStore((s) => s.reputation.score);
|
||||||
const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization);
|
const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization);
|
||||||
@@ -33,13 +33,13 @@ export function DashboardPage() {
|
|||||||
</TutorialHint>
|
</TutorialHint>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{totalDCs > 0 && trainedModels.length === 0 && !activeTraining && (
|
{totalDCs > 0 && baseModels.length === 0 && activePipelines.length === 0 && (
|
||||||
<TutorialHint id="train-first-model">
|
<TutorialHint id="train-first-model">
|
||||||
You have compute available! Head to the Models tab to allocate compute for training and start your first model.
|
You have compute available! Head to the Models tab to allocate compute for training and start your first model.
|
||||||
</TutorialHint>
|
</TutorialHint>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{trainedModels.length > 0 && !trainedModels.some(m => m.isDeployed) && (
|
{baseModels.length > 0 && !baseModels.some(m => m.isDeployed) && (
|
||||||
<TutorialHint id="deploy-model">
|
<TutorialHint id="deploy-model">
|
||||||
Your model is trained! Deploy it from the Models tab to start serving customers and earning revenue.
|
Your model is trained! Deploy it from the Models tab to start serving customers and earning revenue.
|
||||||
</TutorialHint>
|
</TutorialHint>
|
||||||
@@ -66,8 +66,8 @@ export function DashboardPage() {
|
|||||||
<StatCard
|
<StatCard
|
||||||
icon={Brain}
|
icon={Brain}
|
||||||
label="Models"
|
label="Models"
|
||||||
value={trainedModels.length.toString()}
|
value={baseModels.length.toString()}
|
||||||
subValue={activeTraining ? `Training: ${Math.floor((activeTraining.progressTicks / activeTraining.totalTicks) * 100)}%` : 'Idle'}
|
subValue={activePipelines.filter(p => p.status === 'active').length > 0 ? `Training: ${activePipelines.filter(p => p.status === 'active').length} active` : 'Idle'}
|
||||||
color="text-purple-400"
|
color="text-purple-400"
|
||||||
onClick={() => useGameStore.getState().setActivePage('models')}
|
onClick={() => useGameStore.getState().setActivePage('models')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ export function LeaderboardPage() {
|
|||||||
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
||||||
const era = useGameStore((s) => s.meta.currentEra);
|
const era = useGameStore((s) => s.meta.currentEra);
|
||||||
const tickCount = useGameStore((s) => s.meta.tickCount);
|
const tickCount = useGameStore((s) => s.meta.tickCount);
|
||||||
const bestModel = useGameStore((s) =>
|
const bestModel = useGameStore((s) => s.models.bestDeployedModelScore);
|
||||||
s.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -38,10 +38,7 @@ export function MarketPage() {
|
|||||||
const tokensDemand = useGameStore((s) => s.compute.tokensPerSecondDemand);
|
const tokensDemand = useGameStore((s) => s.compute.tokensPerSecondDemand);
|
||||||
const currentEra = useGameStore((s) => s.meta.currentEra);
|
const currentEra = useGameStore((s) => s.meta.currentEra);
|
||||||
const reputationScore = useGameStore((s) => s.reputation.score);
|
const reputationScore = useGameStore((s) => s.reputation.score);
|
||||||
const bestQuality = useGameStore((s) => {
|
const bestQuality = useGameStore((s) => s.models.bestDeployedModelScore / 100);
|
||||||
const deployed = s.models.trainedModels.filter(m => m.isDeployed);
|
|
||||||
return deployed.length > 0 ? Math.max(...deployed.map(m => m.benchmarkScore)) / 100 : 0;
|
|
||||||
});
|
|
||||||
const setProductPricing = useGameStore((s) => s.setProductPricing);
|
const setProductPricing = useGameStore((s) => s.setProductPricing);
|
||||||
const setOverloadPolicy = useGameStore((s) => s.setOverloadPolicy);
|
const setOverloadPolicy = useGameStore((s) => s.setOverloadPolicy);
|
||||||
const pricingFeedback = useAppliedFeedback();
|
const pricingFeedback = useAppliedFeedback();
|
||||||
|
|||||||
+938
-135
File diff suppressed because it is too large
Load Diff
+193
-17
@@ -6,13 +6,17 @@ import type {
|
|||||||
ResearchState, ModelsState, MarketState,
|
ResearchState, ModelsState, MarketState,
|
||||||
CompetitorState, TalentState, DataState,
|
CompetitorState, TalentState, DataState,
|
||||||
ReputationState, AchievementState,
|
ReputationState, AchievementState,
|
||||||
Cluster, Campus, DataCenter, DCTier, RackSkuId, TrainingJob,
|
Cluster, Campus, DataCenter, DCTier, RackSkuId,
|
||||||
ActiveResearch, OwnedDataset, LocationId,
|
ActiveResearch, OwnedDataset, LocationId,
|
||||||
DeploymentCohort, PipelineStage,
|
DeploymentCohort, PipelineStage,
|
||||||
CampusRetrofitQueue,
|
CampusRetrofitQueue,
|
||||||
CoolingType, NetworkFabric,
|
CoolingType, NetworkFabric,
|
||||||
|
FundingRoundType, OverloadPolicy,
|
||||||
|
TrainingPipeline, ModelFamily, DataMixAllocation,
|
||||||
|
ModelArchitecture,
|
||||||
|
SFTSpecialization, QuantizationLevel, VariantCreationJob,
|
||||||
|
EvalJob,
|
||||||
} from '@ai-tycoon/shared';
|
} from '@ai-tycoon/shared';
|
||||||
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
|
|
||||||
import {
|
import {
|
||||||
INITIAL_SETTINGS, SAVE_VERSION,
|
INITIAL_SETTINGS, SAVE_VERSION,
|
||||||
INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE,
|
INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE,
|
||||||
@@ -29,9 +33,15 @@ import {
|
|||||||
estimateNetworkSlots, maxComputeRacks,
|
estimateNetworkSlots, maxComputeRacks,
|
||||||
uuid,
|
uuid,
|
||||||
COOLING_TYPE_CONFIGS, COOLING_ORDER, NETWORK_FABRIC_CONFIGS, FABRIC_ORDER,
|
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';
|
} from '@ai-tycoon/shared';
|
||||||
import {
|
import {
|
||||||
emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary,
|
emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary,
|
||||||
|
BENCHMARKS,
|
||||||
} from '@ai-tycoon/game-engine';
|
} from '@ai-tycoon/game-engine';
|
||||||
import { INITIAL_RIVALS } 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;
|
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => void;
|
||||||
upgradeCoolingType: (dataCenterId: string, targetCooling: CoolingType) => void;
|
upgradeCoolingType: (dataCenterId: string, targetCooling: CoolingType) => void;
|
||||||
upgradeNetworkFabric: (dataCenterId: string, targetFabric: NetworkFabric) => 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;
|
deployModel: (modelId: string) => void;
|
||||||
|
deployVariant: (familyId: string, variantId: string) => void;
|
||||||
setProductPricing: (productLineId: string, field: string, value: number) => void;
|
setProductPricing: (productLineId: string, field: string, value: number) => void;
|
||||||
toggleProductLine: (productLineId: string) => void;
|
toggleProductLine: (productLineId: string) => void;
|
||||||
startResearch: (research: ActiveResearch) => void;
|
startResearch: (research: ActiveResearch) => void;
|
||||||
@@ -107,7 +124,6 @@ interface Actions {
|
|||||||
raiseFunding: (roundType: FundingRoundType) => void;
|
raiseFunding: (roundType: FundingRoundType) => void;
|
||||||
openSourceModel: (modelId: string) => void;
|
openSourceModel: (modelId: string) => void;
|
||||||
setOverloadPolicy: (policy: Partial<OverloadPolicy>) => void;
|
setOverloadPolicy: (policy: Partial<OverloadPolicy>) => void;
|
||||||
setModelTuning: (modelId: string, tuning: Partial<ModelTuning>) => void;
|
|
||||||
acquireCompetitor: (competitorId: string) => void;
|
acquireCompetitor: (competitorId: string) => void;
|
||||||
updateState: (partial: Partial<GameState>) => void;
|
updateState: (partial: Partial<GameState>) => void;
|
||||||
}
|
}
|
||||||
@@ -873,17 +889,175 @@ export const useGameStore = create<Store>()(
|
|||||||
|
|
||||||
// --- Non-infrastructure actions (unchanged) ---
|
// --- 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: {
|
models: {
|
||||||
...s.models,
|
...s.models,
|
||||||
activeTraining: { ...job, progressTicks: 0 },
|
families: [...s.models.families, family],
|
||||||
|
activeTrainingPipelines: [...s.models.activeTrainingPipelines, pipeline],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
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) => ({
|
deployModel: (modelId) => set((s) => ({
|
||||||
models: {
|
models: {
|
||||||
...s.models,
|
...s.models,
|
||||||
trainedModels: s.models.trainedModels.map(m =>
|
baseModels: s.models.baseModels.map(m =>
|
||||||
m.id === modelId ? { ...m, isDeployed: true } : m,
|
m.id === modelId ? { ...m, isDeployed: true } : m,
|
||||||
),
|
),
|
||||||
productLines: s.models.productLines.map(pl => ({
|
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) => ({
|
setProductPricing: (productLineId, field, value) => set((s) => ({
|
||||||
models: {
|
models: {
|
||||||
...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) => {
|
acquireCompetitor: (competitorId) => set((s) => {
|
||||||
const rival = s.competitors.rivals.find(r => r.id === competitorId);
|
const rival = s.competitors.rivals.find(r => r.id === competitorId);
|
||||||
if (!rival || rival.status === 'acquired') return s;
|
if (!rival || rival.status === 'acquired') return s;
|
||||||
@@ -1058,7 +1234,7 @@ export const useGameStore = create<Store>()(
|
|||||||
notifications: [{
|
notifications: [{
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
title: 'Save Reset',
|
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,
|
type: 'info' as const,
|
||||||
tick: 0,
|
tick: 0,
|
||||||
read: false,
|
read: false,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const ACHIEVEMENT_DEFINITIONS: AchievementDefinition[] = [
|
|||||||
name: 'Hello World',
|
name: 'Hello World',
|
||||||
description: 'Train your first AI model.',
|
description: 'Train your first AI model.',
|
||||||
icon: 'Brain',
|
icon: 'Brain',
|
||||||
condition: { field: 'models.trainedModels.length', operator: 'gte', value: 1 },
|
condition: { field: 'models.baseModels.length', operator: 'gte', value: 1 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'first-deploy',
|
id: 'first-deploy',
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import type { BenchmarkDefinition } from '@ai-tycoon/shared';
|
||||||
|
|
||||||
|
export const BENCHMARKS: BenchmarkDefinition[] = [
|
||||||
|
{
|
||||||
|
id: 'arc-challenge',
|
||||||
|
name: 'ARC Challenge',
|
||||||
|
category: 'reasoning',
|
||||||
|
description: 'Advanced reasoning and comprehension tasks requiring multi-step inference.',
|
||||||
|
primaryCapability: 'reasoning',
|
||||||
|
secondaryCapability: 'knowledge',
|
||||||
|
computeCost: 0.001,
|
||||||
|
ticksToRun: 8,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.3, enterprise: 0.5, developer: 0.4, research: 0.8 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'codeforce',
|
||||||
|
name: 'CodeForce',
|
||||||
|
category: 'coding',
|
||||||
|
description: 'Competitive programming and software engineering benchmarks.',
|
||||||
|
primaryCapability: 'coding',
|
||||||
|
secondaryCapability: 'reasoning',
|
||||||
|
computeCost: 0.001,
|
||||||
|
ticksToRun: 8,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.2, enterprise: 0.7, developer: 0.9, research: 0.5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mathquest',
|
||||||
|
name: 'MathQuest',
|
||||||
|
category: 'math',
|
||||||
|
description: 'Mathematical problem-solving from algebra to graduate-level proofs.',
|
||||||
|
primaryCapability: 'math',
|
||||||
|
secondaryCapability: 'reasoning',
|
||||||
|
computeCost: 0.001,
|
||||||
|
ticksToRun: 8,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.1, enterprise: 0.6, developer: 0.5, research: 0.9 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'worldfacts',
|
||||||
|
name: 'WorldFacts',
|
||||||
|
category: 'knowledge',
|
||||||
|
description: 'Broad factual knowledge across science, history, culture, and current events.',
|
||||||
|
primaryCapability: 'knowledge',
|
||||||
|
secondaryCapability: 'reasoning',
|
||||||
|
computeCost: 0.001,
|
||||||
|
ticksToRun: 6,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.5, enterprise: 0.4, developer: 0.3, research: 0.6 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'chatrank',
|
||||||
|
name: 'ChatRank',
|
||||||
|
category: 'chat',
|
||||||
|
description: 'Human preference evaluation of conversational quality, helpfulness, and creativity.',
|
||||||
|
primaryCapability: 'creative',
|
||||||
|
secondaryCapability: 'knowledge',
|
||||||
|
computeCost: 0.002,
|
||||||
|
ticksToRun: 10,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.9, enterprise: 0.3, developer: 0.2, research: 0.2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'harmguard',
|
||||||
|
name: 'HarmGuard',
|
||||||
|
category: 'safety',
|
||||||
|
description: 'Safety evaluation measuring harm avoidance, truthfulness, and responsible behavior.',
|
||||||
|
primaryCapability: 'reasoning',
|
||||||
|
computeCost: 0.001,
|
||||||
|
ticksToRun: 8,
|
||||||
|
unlockedAtEra: 'startup',
|
||||||
|
marketRelevance: { consumer: 0.4, enterprise: 0.9, developer: 0.3, research: 0.7 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'visionbench',
|
||||||
|
name: 'VisionBench',
|
||||||
|
category: 'multimodal',
|
||||||
|
description: 'Image understanding, visual reasoning, and multimodal comprehension.',
|
||||||
|
primaryCapability: 'multimodal',
|
||||||
|
secondaryCapability: 'reasoning',
|
||||||
|
computeCost: 0.003,
|
||||||
|
ticksToRun: 12,
|
||||||
|
unlockedAtEra: 'scaleup',
|
||||||
|
marketRelevance: { consumer: 0.5, enterprise: 0.6, developer: 0.6, research: 0.7 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'agentarena',
|
||||||
|
name: 'AgentArena',
|
||||||
|
category: 'agents',
|
||||||
|
description: 'Autonomous agent tasks: tool use, multi-step planning, and environment interaction.',
|
||||||
|
primaryCapability: 'agents',
|
||||||
|
secondaryCapability: 'coding',
|
||||||
|
computeCost: 0.005,
|
||||||
|
ticksToRun: 15,
|
||||||
|
unlockedAtEra: 'bigtech',
|
||||||
|
marketRelevance: { consumer: 0.3, enterprise: 0.8, developer: 0.7, research: 0.6 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'frontier-eval',
|
||||||
|
name: 'Frontier Eval',
|
||||||
|
category: 'reasoning',
|
||||||
|
description: 'Cutting-edge capability evaluation at the frontier of AI research.',
|
||||||
|
primaryCapability: 'reasoning',
|
||||||
|
secondaryCapability: 'math',
|
||||||
|
computeCost: 0.01,
|
||||||
|
ticksToRun: 20,
|
||||||
|
unlockedAtEra: 'agi',
|
||||||
|
marketRelevance: { consumer: 0.2, enterprise: 0.5, developer: 0.5, research: 1.0 },
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -8,3 +8,4 @@ export { TECH_TREE } from './data/techTree';
|
|||||||
export { INITIAL_RIVALS } from './data/competitors';
|
export { INITIAL_RIVALS } from './data/competitors';
|
||||||
export { KEY_HIRE_POOL } from './data/keyHires';
|
export { KEY_HIRE_POOL } from './data/keyHires';
|
||||||
export { ACHIEVEMENT_DEFINITIONS } from './data/achievements';
|
export { ACHIEVEMENT_DEFINITIONS } from './data/achievements';
|
||||||
|
export { BENCHMARKS } from './data/benchmarks';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const ERA_INDEX: Record<string, number> = { startup: 0, scaleup: 1, bigtech: 2,
|
|||||||
|
|
||||||
function getFieldValue(state: GameState, field: string): number {
|
function getFieldValue(state: GameState, field: string): number {
|
||||||
if (field === 'meta._eraIndex') return ERA_INDEX[state.meta.currentEra] ?? 0;
|
if (field === 'meta._eraIndex') return ERA_INDEX[state.meta.currentEra] ?? 0;
|
||||||
if (field === 'meta._deployedModelCount') return state.models.trainedModels.filter(m => m.isDeployed).length;
|
if (field === 'meta._deployedModelCount') return state.models.baseModels.filter(m => m.isDeployed).length;
|
||||||
const parts = field.split('.');
|
const parts = field.split('.');
|
||||||
let current: unknown = state;
|
let current: unknown = state;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function processCompetitors(state: GameState): CompetitorState {
|
|||||||
|
|
||||||
const allCaps = [
|
const allCaps = [
|
||||||
...rivals.filter(r => r.status === 'active').map(r => r.estimatedCapability),
|
...rivals.filter(r => r.status === 'active').map(r => r.estimatedCapability),
|
||||||
state.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0),
|
state.models.bestDeployedModelScore,
|
||||||
];
|
];
|
||||||
const industryBenchmark = allCaps.length > 0 ? Math.max(...allCaps) : 0;
|
const industryBenchmark = allCaps.length > 0 ? Math.max(...allCaps) : 0;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function processEconomy(
|
|||||||
const talentExpenses = state.talent.totalSalaryPerTick;
|
const talentExpenses = state.talent.totalSalaryPerTick;
|
||||||
const dataExpenses = state.data.partnerships.reduce((sum, p) => sum + p.costPerTick, 0);
|
const dataExpenses = state.data.partnerships.reduce((sum, p) => sum + p.costPerTick, 0);
|
||||||
|
|
||||||
const bestCapability = state.models.trainedModels.reduce((best, m) => Math.max(best, m.benchmarkScore), 0);
|
const bestCapability = state.models.bestDeployedModelScore;
|
||||||
const eraIdx = ['startup', 'scaleup', 'bigtech', 'agi'].indexOf(state.meta.currentEra);
|
const eraIdx = ['startup', 'scaleup', 'bigtech', 'agi'].indexOf(state.meta.currentEra);
|
||||||
const complianceCost = bestCapability > 30 ? bestCapability * REGULATION_COMPLIANCE_PER_CAPABILITY * (1 + eraIdx * 0.5) / 100 : 0;
|
const complianceCost = bestCapability > 30 ? bestCapability * REGULATION_COMPLIANCE_PER_CAPABILITY * (1 + eraIdx * 0.5) / 100 : 0;
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ export function checkEraTransition(state: GameState): Era | null {
|
|||||||
const thresholds = ERA_THRESHOLDS[nextEra as keyof typeof ERA_THRESHOLDS];
|
const thresholds = ERA_THRESHOLDS[nextEra as keyof typeof ERA_THRESHOLDS];
|
||||||
if (!thresholds) return null;
|
if (!thresholds) return null;
|
||||||
|
|
||||||
const bestModel = state.models.trainedModels.reduce(
|
const bestModel = state.models.bestDeployedModelScore;
|
||||||
(best, m) => Math.max(best, m.benchmarkScore), 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
state.economy.totalRevenue >= thresholds.revenue &&
|
state.economy.totalRevenue >= thresholds.revenue &&
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ export function canRaiseFunding(state: GameState): { canRaise: boolean; nextRoun
|
|||||||
export function computeValuation(state: GameState): number {
|
export function computeValuation(state: GameState): number {
|
||||||
const revenueMultiple = state.economy.revenuePerTick * 86400 * 365;
|
const revenueMultiple = state.economy.revenuePerTick * 86400 * 365;
|
||||||
const subscriberValue = state.market.consumers.totalSubscribers * 500;
|
const subscriberValue = state.market.consumers.totalSubscribers * 500;
|
||||||
const capabilityValue = Math.pow(
|
const capabilityValue = Math.pow(state.models.bestDeployedModelScore, 2) * 1000;
|
||||||
Math.max(...state.models.trainedModels.map(m => m.benchmarkScore), 0),
|
|
||||||
2,
|
|
||||||
) * 1000;
|
|
||||||
return Math.max(100_000, revenueMultiple * 10 + subscriberValue + capabilityValue);
|
return Math.max(100_000, revenueMultiple * 10 + subscriberValue + capabilityValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { GameState, MarketState } from '@ai-tycoon/shared';
|
import type { GameState, MarketState, BenchmarkResult } from '@ai-tycoon/shared';
|
||||||
import {
|
import {
|
||||||
CONSUMER_BASE_GROWTH,
|
CONSUMER_BASE_GROWTH,
|
||||||
CONSUMER_QUALITY_GROWTH_MULTIPLIER,
|
CONSUMER_QUALITY_GROWTH_MULTIPLIER,
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
MARKET_CAP_REPUTATION_BONUS,
|
MARKET_CAP_REPUTATION_BONUS,
|
||||||
OVERLOAD_PENALTY_EXPONENT,
|
OVERLOAD_PENALTY_EXPONENT,
|
||||||
} from '@ai-tycoon/shared';
|
} from '@ai-tycoon/shared';
|
||||||
|
import { BENCHMARKS } from '../data/benchmarks';
|
||||||
|
|
||||||
export interface MarketTickResult {
|
export interface MarketTickResult {
|
||||||
marketState: MarketState;
|
marketState: MarketState;
|
||||||
@@ -21,12 +22,39 @@ export interface MarketTickResult {
|
|||||||
totalTokenDemand: number;
|
totalTokenDemand: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processMarket(state: GameState, currentTickCapacity: number): MarketTickResult {
|
function getSegmentQuality(
|
||||||
const bestModel = state.models.trainedModels
|
segment: 'consumer' | 'enterprise' | 'developer' | 'research',
|
||||||
.filter(m => m.isDeployed)
|
benchmarkResults: BenchmarkResult[],
|
||||||
.sort((a, b) => b.benchmarkScore - a.benchmarkScore)[0];
|
fallbackScore: number,
|
||||||
|
): number {
|
||||||
|
if (benchmarkResults.length === 0) return fallbackScore / 100;
|
||||||
|
|
||||||
const modelQuality = bestModel ? bestModel.benchmarkScore / 100 : 0;
|
const bestByBenchmark = new Map<string, number>();
|
||||||
|
for (const r of benchmarkResults) {
|
||||||
|
const prev = bestByBenchmark.get(r.benchmarkId) ?? 0;
|
||||||
|
if (r.score > prev) bestByBenchmark.set(r.benchmarkId, r.score);
|
||||||
|
}
|
||||||
|
|
||||||
|
let weightedSum = 0;
|
||||||
|
let totalWeight = 0;
|
||||||
|
for (const bench of BENCHMARKS) {
|
||||||
|
const score = bestByBenchmark.get(bench.id);
|
||||||
|
if (score == null) continue;
|
||||||
|
const weight = bench.marketRelevance[segment];
|
||||||
|
weightedSum += (score / 100) * weight;
|
||||||
|
totalWeight += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight === 0) return fallbackScore / 100;
|
||||||
|
return weightedSum / totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processMarket(state: GameState, currentTickCapacity: number): MarketTickResult {
|
||||||
|
const consumerQuality = getSegmentQuality('consumer', state.models.benchmarkResults, state.models.bestDeployedModelScore);
|
||||||
|
const enterpriseQuality = getSegmentQuality('enterprise', state.models.benchmarkResults, state.models.bestDeployedModelScore);
|
||||||
|
const modelQuality = state.models.benchmarkResults.length > 0
|
||||||
|
? (consumerQuality + enterpriseQuality) / 2
|
||||||
|
: state.models.bestDeployedModelScore / 100;
|
||||||
const chatProduct = state.models.productLines.find(p => p.type === 'chat-product');
|
const chatProduct = state.models.productLines.find(p => p.type === 'chat-product');
|
||||||
const textApi = state.models.productLines.find(p => p.type === 'text-api');
|
const textApi = state.models.productLines.find(p => p.type === 'text-api');
|
||||||
|
|
||||||
@@ -34,7 +62,7 @@ export function processMarket(state: GameState, currentTickCapacity: number): Ma
|
|||||||
const consumers = { ...state.market.consumers };
|
const consumers = { ...state.market.consumers };
|
||||||
let subscriptionRevenue = 0;
|
let subscriptionRevenue = 0;
|
||||||
|
|
||||||
if (chatProduct?.isActive && bestModel) {
|
if (chatProduct?.isActive && modelQuality > 0) {
|
||||||
const price = chatProduct.pricing.subscriptionPrice;
|
const price = chatProduct.pricing.subscriptionPrice;
|
||||||
const fairPrice = 20 + modelQuality * 80;
|
const fairPrice = 20 + modelQuality * 80;
|
||||||
const priceRatio = price / Math.max(1, fairPrice);
|
const priceRatio = price / Math.max(1, fairPrice);
|
||||||
@@ -109,7 +137,7 @@ export function processMarket(state: GameState, currentTickCapacity: number): Ma
|
|||||||
let apiRevenue = 0;
|
let apiRevenue = 0;
|
||||||
let organicApiTokens = 0;
|
let organicApiTokens = 0;
|
||||||
|
|
||||||
if (textApi?.isActive && bestModel) {
|
if (textApi?.isActive && modelQuality > 0) {
|
||||||
const reputationFactor = state.reputation.score / 100;
|
const reputationFactor = state.reputation.score / 100;
|
||||||
const qualityFactor = modelQuality;
|
const qualityFactor = modelQuality;
|
||||||
const priceFactor = Math.max(0.1, 1 - (textApi.pricing.outputTokenPrice / 20));
|
const priceFactor = Math.max(0.1, 1 - (textApi.pricing.outputTokenPrice / 20));
|
||||||
|
|||||||
@@ -1,21 +1,40 @@
|
|||||||
import type { GameState, ModelsState, TrainedModel, ModelCapabilities } from '@ai-tycoon/shared';
|
import type {
|
||||||
import { uuid, VRAM_REQUIREMENTS_BY_GENERATION } from '@ai-tycoon/shared';
|
GameState, ModelsState, BaseModel, ModelCapabilities, SafetyProfile,
|
||||||
|
TrainingPipeline, TrainingEvent, TrainingEventType,
|
||||||
|
ModelVariant, VariantCreationJob, EvalJob, BenchmarkResult,
|
||||||
|
BenchmarkDefinition,
|
||||||
|
} from '@ai-tycoon/shared';
|
||||||
|
import { BENCHMARKS } from '../data/benchmarks';
|
||||||
|
import {
|
||||||
|
uuid, VRAM_REQUIREMENTS_BY_GENERATION,
|
||||||
|
SFT_TIME_FRACTION, SFT_COMPUTE_FRACTION,
|
||||||
|
ALIGNMENT_TIME_FRACTION, ALIGNMENT_COMPUTE_FRACTION,
|
||||||
|
MOE_CAPABILITY_MULTIPLIER, MOE_SPEED_MULTIPLIER,
|
||||||
|
EVENT_BASE_PROBABILITY,
|
||||||
|
LOSS_SPIKE_DELAY_MIN, LOSS_SPIKE_DELAY_MAX,
|
||||||
|
INSTABILITY_PROGRESS_LOSS_MIN, INSTABILITY_PROGRESS_LOSS_MAX,
|
||||||
|
BREAKTHROUGH_CAPABILITY_BONUS_MIN, BREAKTHROUGH_CAPABILITY_BONUS_MAX,
|
||||||
|
EMERGENT_CAPABILITY_THRESHOLDS,
|
||||||
|
ALIGNMENT_METHODS,
|
||||||
|
SFT_SPECIALIZATION_BONUSES,
|
||||||
|
QUANTIZATION_CONFIGS,
|
||||||
|
DISTILLATION_BASE_RETENTION,
|
||||||
|
QUANTIZATION_TICKS,
|
||||||
|
} from '@ai-tycoon/shared';
|
||||||
|
|
||||||
export interface ModelTickResult {
|
export interface ModelTickResult {
|
||||||
modelsState: ModelsState;
|
modelsState: ModelsState;
|
||||||
modelCompleted: TrainedModel | null;
|
completedModels: BaseModel[];
|
||||||
|
notifications: { title: string; message: string; type: 'success' | 'warning' | 'info' }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processModels(state: GameState): ModelTickResult {
|
export function processModels(state: GameState): ModelTickResult {
|
||||||
const active = state.models.activeTraining;
|
const completedModels: BaseModel[] = [];
|
||||||
if (!active) {
|
const notifications: ModelTickResult['notifications'] = [];
|
||||||
return { modelsState: state.models, modelCompleted: null };
|
let baseModels = [...state.models.baseModels];
|
||||||
}
|
let families = [...state.models.families];
|
||||||
|
|
||||||
const requiredVram = VRAM_REQUIREMENTS_BY_GENERATION[active.generation] ?? 0;
|
const totalTrainingFlops = state.compute.totalTrainingFlops * state.compute.trainingAllocation;
|
||||||
if (requiredVram > 0 && state.compute.totalVramGB < requiredVram) {
|
|
||||||
return { modelsState: state.models, modelCompleted: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const researcherBoost = state.talent.departments.research.headcount *
|
const researcherBoost = state.talent.departments.research.headcount *
|
||||||
state.talent.departments.research.effectiveness;
|
state.talent.departments.research.effectiveness;
|
||||||
@@ -23,82 +42,487 @@ export function processModels(state: GameState): ModelTickResult {
|
|||||||
state.talent.departments.engineering.effectiveness;
|
state.talent.departments.engineering.effectiveness;
|
||||||
const speedMultiplier = 1 + (researcherBoost + engineerBoost) * 0.05;
|
const speedMultiplier = 1 + (researcherBoost + engineerBoost) * 0.05;
|
||||||
|
|
||||||
const newProgress = active.progressTicks + speedMultiplier;
|
const updatedPipelines: TrainingPipeline[] = [];
|
||||||
|
|
||||||
if (newProgress >= active.totalTicks) {
|
for (const pipeline of state.models.activeTrainingPipelines) {
|
||||||
const model = createTrainedModel(active.modelName, active.generation, active.allocatedCompute, active.allocatedDataTokens, state);
|
if (pipeline.status !== 'active') {
|
||||||
|
updatedPipelines.push(pipeline);
|
||||||
return {
|
continue;
|
||||||
modelsState: {
|
|
||||||
...state.models,
|
|
||||||
trainedModels: [...state.models.trainedModels, model],
|
|
||||||
activeTraining: null,
|
|
||||||
},
|
|
||||||
modelCompleted: model,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const generation = families.find(f => f.id === pipeline.familyId)?.generation ?? 1;
|
||||||
|
const requiredVram = VRAM_REQUIREMENTS_BY_GENERATION[generation] ?? 0;
|
||||||
|
if (requiredVram > 0 && state.compute.totalVramGB < requiredVram) {
|
||||||
|
updatedPipelines.push({ ...pipeline, status: 'stalled' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveFlops = totalTrainingFlops * pipeline.allocatedComputeFraction;
|
||||||
|
let updated = { ...pipeline, events: [...pipeline.events] };
|
||||||
|
|
||||||
|
if (pipeline.currentStage === 'pretraining') {
|
||||||
|
const stage = { ...pipeline.stages.pretraining };
|
||||||
|
const newProgress = stage.progressTicks + speedMultiplier;
|
||||||
|
|
||||||
|
const events = generateTrainingEvents(pipeline, state);
|
||||||
|
let tickDelay = 0;
|
||||||
|
let progressLost = 0;
|
||||||
|
for (const event of events) {
|
||||||
|
updated.events.push(event);
|
||||||
|
if (event.type === 'loss_spike') {
|
||||||
|
tickDelay += event.impact.ticksDelayed ?? 0;
|
||||||
|
notifications.push({ title: 'Loss Spike', message: `${pipeline.modelName}: Training loss spiked! Delayed ${event.impact.ticksDelayed} ticks.`, type: 'warning' });
|
||||||
|
} else if (event.type === 'instability') {
|
||||||
|
progressLost += event.impact.progressLost ?? 0;
|
||||||
|
notifications.push({ title: 'Training Instability', message: `${pipeline.modelName}: Rolled back to checkpoint. Lost ${Math.round((event.impact.progressLost ?? 0) * 100)}% progress.`, type: 'warning' });
|
||||||
|
} else if (event.type === 'breakthrough') {
|
||||||
|
notifications.push({ title: 'Breakthrough!', message: `${pipeline.modelName}: Unexpected capability jump in ${event.impact.capabilityDomain}!`, type: 'success' });
|
||||||
|
} else if (event.type === 'hardware_failure') {
|
||||||
|
tickDelay += event.impact.ticksDelayed ?? 0;
|
||||||
|
notifications.push({ title: 'Hardware Failure', message: `${pipeline.modelName}: GPU failure during training. Recovering from checkpoint.`, type: 'warning' });
|
||||||
|
} else if (event.type === 'data_contamination') {
|
||||||
|
notifications.push({ title: 'Data Contamination', message: `${pipeline.modelName}: Copyright concerns detected in training data.`, type: 'warning' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveProgress = Math.max(0, newProgress - tickDelay - (stage.totalTicks * progressLost));
|
||||||
|
stage.progressTicks = effectiveProgress;
|
||||||
|
stage.computeAllocated = effectiveFlops;
|
||||||
|
stage.lossValue = Math.max(0.01, 10 * Math.exp(-stage.progressTicks / stage.totalTicks * 3));
|
||||||
|
|
||||||
|
if (stage.progressTicks >= stage.totalTicks) {
|
||||||
|
stage.isComplete = true;
|
||||||
|
stage.progressTicks = stage.totalTicks;
|
||||||
|
|
||||||
|
if (updated.stages.sft) {
|
||||||
|
updated.currentStage = 'sft';
|
||||||
|
notifications.push({ title: 'Pre-training Complete', message: `${pipeline.modelName}: Moving to supervised fine-tuning.`, type: 'info' });
|
||||||
|
} else if (updated.stages.alignment) {
|
||||||
|
updated.currentStage = 'alignment';
|
||||||
|
notifications.push({ title: 'Pre-training Complete', message: `${pipeline.modelName}: Moving to alignment.`, type: 'info' });
|
||||||
|
} else {
|
||||||
|
const model = createBaseModel(updated, state);
|
||||||
|
baseModels = [...baseModels, model];
|
||||||
|
families = families.map(f =>
|
||||||
|
f.id === pipeline.familyId ? { ...f, baseModelId: model.id } : f,
|
||||||
|
);
|
||||||
|
completedModels.push(model);
|
||||||
|
updated.status = 'completed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updated = { ...updated, stages: { ...updated.stages, pretraining: stage } };
|
||||||
|
} else if (pipeline.currentStage === 'sft' && pipeline.stages.sft) {
|
||||||
|
const stage = { ...pipeline.stages.sft };
|
||||||
|
stage.progressTicks += speedMultiplier;
|
||||||
|
|
||||||
|
if (stage.progressTicks >= stage.totalTicks) {
|
||||||
|
stage.isComplete = true;
|
||||||
|
stage.progressTicks = stage.totalTicks;
|
||||||
|
|
||||||
|
if (updated.stages.alignment) {
|
||||||
|
updated.currentStage = 'alignment';
|
||||||
|
notifications.push({ title: 'SFT Complete', message: `${pipeline.modelName}: Moving to alignment.`, type: 'info' });
|
||||||
|
} else {
|
||||||
|
const model = createBaseModel(updated, state);
|
||||||
|
baseModels = [...baseModels, model];
|
||||||
|
families = families.map(f =>
|
||||||
|
f.id === pipeline.familyId ? { ...f, baseModelId: model.id } : f,
|
||||||
|
);
|
||||||
|
completedModels.push(model);
|
||||||
|
updated.status = 'completed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updated = { ...updated, stages: { ...updated.stages, sft: stage } };
|
||||||
|
} else if (pipeline.currentStage === 'alignment' && pipeline.stages.alignment) {
|
||||||
|
const stage = { ...pipeline.stages.alignment };
|
||||||
|
stage.progressTicks += speedMultiplier;
|
||||||
|
|
||||||
|
if (stage.progressTicks >= stage.totalTicks) {
|
||||||
|
stage.isComplete = true;
|
||||||
|
stage.progressTicks = stage.totalTicks;
|
||||||
|
|
||||||
|
const model = createBaseModel(updated, state);
|
||||||
|
baseModels = [...baseModels, model];
|
||||||
|
families = families.map(f =>
|
||||||
|
f.id === pipeline.familyId ? { ...f, baseModelId: model.id } : f,
|
||||||
|
);
|
||||||
|
completedModels.push(model);
|
||||||
|
updated.status = 'completed';
|
||||||
|
}
|
||||||
|
updated = { ...updated, stages: { ...updated.stages, alignment: stage } };
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPipelines.push(updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedVariantJobs = processVariantJobs(state, speedMultiplier);
|
||||||
|
for (const variant of updatedVariantJobs.newVariants) {
|
||||||
|
variant.createdAtTick = state.meta.tickCount;
|
||||||
|
families = families.map(f =>
|
||||||
|
f.id === variant.familyId ? { ...f, variants: [...f.variants, variant] } : f,
|
||||||
|
);
|
||||||
|
notifications.push({
|
||||||
|
title: 'Variant Created',
|
||||||
|
message: `${variant.name} (${variant.variantType}) is ready!`,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedEvalJobs = processEvalJobs(state);
|
||||||
|
|
||||||
|
const allDeployed = [
|
||||||
|
...baseModels.filter(m => m.isDeployed),
|
||||||
|
...families.flatMap(f => f.variants.filter(v => v.isDeployed)),
|
||||||
|
];
|
||||||
|
|
||||||
|
const bestDeployedModelScore = allDeployed.reduce((best, m) =>
|
||||||
|
Math.max(best, 'rawCapability' in m ? m.rawCapability : computeVariantScore(m)), 0);
|
||||||
|
|
||||||
|
const bestDeployedSafetyScore = allDeployed.reduce((best, m) =>
|
||||||
|
Math.max(best, m.safetyProfile.overallSafety), 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modelsState: {
|
modelsState: {
|
||||||
...state.models,
|
...state.models,
|
||||||
activeTraining: { ...active, progressTicks: newProgress },
|
baseModels,
|
||||||
|
families,
|
||||||
|
activeTrainingPipelines: updatedPipelines,
|
||||||
|
variantJobs: updatedVariantJobs.jobs,
|
||||||
|
evalJobs: updatedEvalJobs.jobs,
|
||||||
|
benchmarkResults: [...state.models.benchmarkResults, ...updatedEvalJobs.newResults],
|
||||||
|
bestDeployedModelScore,
|
||||||
|
bestDeployedSafetyScore,
|
||||||
},
|
},
|
||||||
modelCompleted: null,
|
completedModels,
|
||||||
|
notifications,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTrainedModel(
|
function generateTrainingEvents(pipeline: TrainingPipeline, state: GameState): TrainingEvent[] {
|
||||||
name: string,
|
const events: TrainingEvent[] = [];
|
||||||
generation: number,
|
const params = pipeline.architecture.totalParameters;
|
||||||
compute: number,
|
const baseProbability = EVENT_BASE_PROBABILITY * Math.log10(Math.max(1, params));
|
||||||
dataTokens: number,
|
|
||||||
|
const hasInterpretability = state.research.completedResearch.includes('interpretability');
|
||||||
|
const hasDataPipeline = state.research.completedResearch.includes('data-pipeline');
|
||||||
|
const hasRedundancy = state.research.completedResearch.includes('redundancy-protocols');
|
||||||
|
|
||||||
|
if (Math.random() < baseProbability * 2.0) {
|
||||||
|
const delay = LOSS_SPIKE_DELAY_MIN + Math.floor(Math.random() * (LOSS_SPIKE_DELAY_MAX - LOSS_SPIKE_DELAY_MIN));
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'loss_spike', tick: state.meta.tickCount,
|
||||||
|
severity: delay > 15 ? 'major' : delay > 10 ? 'moderate' : 'minor',
|
||||||
|
description: `Training loss spiked to ${(Math.random() * 5 + 2).toFixed(2)}`,
|
||||||
|
resolved: true,
|
||||||
|
impact: { ticksDelayed: delay },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params > 10 && Math.random() < baseProbability * (hasInterpretability ? 0.25 : 0.5)) {
|
||||||
|
const loss = INSTABILITY_PROGRESS_LOSS_MIN + Math.random() * (INSTABILITY_PROGRESS_LOSS_MAX - INSTABILITY_PROGRESS_LOSS_MIN);
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'instability', tick: state.meta.tickCount,
|
||||||
|
severity: loss > 0.12 ? 'major' : 'moderate',
|
||||||
|
description: 'Training run became unstable. Rolling back to last checkpoint.',
|
||||||
|
resolved: true,
|
||||||
|
impact: { progressLost: loss },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const chinchillaRatio = pipeline.stages.pretraining.chinchillaRatio;
|
||||||
|
if (params > 30 && chinchillaRatio > 15 && Math.random() < baseProbability * 0.3) {
|
||||||
|
const capDomains: (keyof ModelCapabilities)[] = ['reasoning', 'coding', 'creative', 'math', 'knowledge', 'agents'];
|
||||||
|
const domain = capDomains[Math.floor(Math.random() * capDomains.length)];
|
||||||
|
const bonus = BREAKTHROUGH_CAPABILITY_BONUS_MIN + Math.floor(Math.random() * (BREAKTHROUGH_CAPABILITY_BONUS_MAX - BREAKTHROUGH_CAPABILITY_BONUS_MIN));
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'breakthrough', tick: state.meta.tickCount,
|
||||||
|
severity: 'major',
|
||||||
|
description: `Unexpected capability jump in ${domain}!`,
|
||||||
|
resolved: true,
|
||||||
|
impact: { capabilityBonus: bonus, capabilityDomain: domain },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [thresholdStr, capName] of Object.entries(EMERGENT_CAPABILITY_THRESHOLDS)) {
|
||||||
|
const threshold = Number(thresholdStr);
|
||||||
|
const prevProgress = pipeline.stages.pretraining.progressTicks;
|
||||||
|
const progressRatio = prevProgress / pipeline.stages.pretraining.totalTicks;
|
||||||
|
if (params >= threshold && progressRatio > 0.5 && progressRatio < 0.55) {
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'emergent_capability', tick: state.meta.tickCount,
|
||||||
|
severity: 'major',
|
||||||
|
description: `Model developed ${capName} capability!`,
|
||||||
|
resolved: true,
|
||||||
|
impact: { capabilityBonus: 10, capabilityDomain: 'reasoning' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const avgLegalRisk = state.data.ownedDatasets.length > 0
|
||||||
|
? state.data.ownedDatasets.reduce((sum, d) => sum + d.legalRisk, 0) / state.data.ownedDatasets.length
|
||||||
|
: 0;
|
||||||
|
if (Math.random() < baseProbability * (hasDataPipeline ? 0.25 : 0.5) * avgLegalRisk) {
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'data_contamination', tick: state.meta.tickCount,
|
||||||
|
severity: 'moderate',
|
||||||
|
description: 'Copyright holders identified content in training data.',
|
||||||
|
resolved: true,
|
||||||
|
impact: {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.random() < baseProbability * (hasRedundancy ? 0.1 : 0.2)) {
|
||||||
|
const delay = 10 + Math.floor(Math.random() * 20);
|
||||||
|
events.push({
|
||||||
|
id: uuid(), type: 'hardware_failure', tick: state.meta.tickCount,
|
||||||
|
severity: delay > 20 ? 'major' : 'moderate',
|
||||||
|
description: 'GPU cluster failure during training. Recovering from checkpoint.',
|
||||||
|
resolved: true,
|
||||||
|
impact: { ticksDelayed: delay },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBaseModel(
|
||||||
|
pipeline: TrainingPipeline,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
): TrainedModel {
|
): BaseModel {
|
||||||
|
const { architecture, dataMix } = pipeline;
|
||||||
|
const compute = pipeline.stages.pretraining.computeAllocated;
|
||||||
|
const dataTokens = pipeline.stages.pretraining.targetTokens;
|
||||||
|
|
||||||
const computeFactor = Math.sqrt(compute) * 5;
|
const computeFactor = Math.sqrt(compute) * 5;
|
||||||
const dataFactor = Math.log10(1 + dataTokens / 1e8) * 10;
|
const dataFactor = Math.log10(1 + dataTokens / 1e8) * 10;
|
||||||
const researchBonus = state.research.completedResearch.length * 3;
|
const researchBonus = state.research.completedResearch.length * 3;
|
||||||
const efficiencyBonus = state.research.completedResearch.filter(r => r.includes('efficiency')).length * 5;
|
const efficiencyBonus = state.research.completedResearch.filter(r => r.includes('efficiency')).length * 5;
|
||||||
|
|
||||||
const baseCapability = Math.min(95, computeFactor + dataFactor + researchBonus + efficiencyBonus);
|
let rawCapability = Math.min(95, computeFactor + dataFactor + researchBonus + efficiencyBonus);
|
||||||
|
|
||||||
|
if (architecture.type === 'moe') {
|
||||||
|
rawCapability = Math.min(98, rawCapability * MOE_CAPABILITY_MULTIPLIER);
|
||||||
|
}
|
||||||
|
|
||||||
const researcherQuality = state.talent.departments.research.effectiveness;
|
const researcherQuality = state.talent.departments.research.effectiveness;
|
||||||
|
|
||||||
const capabilities: ModelCapabilities = {
|
const capabilities: ModelCapabilities = {
|
||||||
reasoning: clamp(baseCapability * (0.8 + Math.random() * 0.4) * (1 + researcherQuality * 0.2)),
|
reasoning: clamp(rawCapability * (0.6 + dataMix.scientific * 0.5 + dataMix.code * 0.3) * (1 + researcherQuality * 0.2)),
|
||||||
coding: clamp(baseCapability * (0.7 + Math.random() * 0.5)),
|
coding: clamp(rawCapability * (0.5 + dataMix.code * 1.0)),
|
||||||
creative: clamp(baseCapability * (0.6 + Math.random() * 0.6)),
|
creative: clamp(rawCapability * (0.4 + dataMix.books * 0.6 + dataMix.conversation * 0.3)),
|
||||||
multimodal: clamp(baseCapability * (0.3 + Math.random() * 0.3)),
|
math: clamp(rawCapability * (0.3 + dataMix.scientific * 0.7 + dataMix.code * 0.2)),
|
||||||
agents: clamp(baseCapability * (0.2 + Math.random() * 0.3)),
|
knowledge: clamp(rawCapability * (0.5 + dataMix.web * 0.3 + dataMix.books * 0.3)),
|
||||||
speed: Math.max(1, 100 - compute * 0.5 + efficiencyBonus * 2),
|
multimodal: clamp(rawCapability * (dataMix.images * 0.5 + dataMix.video * 0.4 + dataMix.audio * 0.2)),
|
||||||
|
agents: clamp(rawCapability * (0.2 + dataMix.code * 0.3 + dataMix.conversation * 0.2)),
|
||||||
|
speed: Math.max(1, 100 - architecture.totalParameters * 0.3 + efficiencyBonus * 2 + (architecture.type === 'moe' ? MOE_SPEED_MULTIPLIER * 10 : 0)),
|
||||||
|
contextUtilization: Math.min(100, architecture.contextWindow * 0.4),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const breakthroughBonuses: Partial<Record<keyof ModelCapabilities, number>> = {};
|
||||||
|
for (const event of pipeline.events) {
|
||||||
|
if ((event.type === 'breakthrough' || event.type === 'emergent_capability') && event.impact.capabilityDomain && event.impact.capabilityBonus) {
|
||||||
|
const domain = event.impact.capabilityDomain;
|
||||||
|
breakthroughBonuses[domain] = (breakthroughBonuses[domain] ?? 0) + event.impact.capabilityBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [domain, bonus] of Object.entries(breakthroughBonuses)) {
|
||||||
|
const key = domain as keyof ModelCapabilities;
|
||||||
|
capabilities[key] = clamp(capabilities[key] + bonus);
|
||||||
|
}
|
||||||
|
|
||||||
|
const completedStages: ('pretraining' | 'sft' | 'alignment')[] = ['pretraining'];
|
||||||
|
|
||||||
|
if (pipeline.stages.sft?.isComplete) {
|
||||||
|
completedStages.push('sft');
|
||||||
|
const sft = pipeline.stages.sft;
|
||||||
|
for (let i = 0; i < sft.specializations.length; i++) {
|
||||||
|
const spec = sft.specializations[i];
|
||||||
|
const bonuses = SFT_SPECIALIZATION_BONUSES[spec];
|
||||||
|
if (!bonuses) continue;
|
||||||
|
const diminishing = i === 0 ? 1.0 : i === 1 ? 0.7 : 0.4;
|
||||||
|
for (const [cap, value] of Object.entries(bonuses)) {
|
||||||
|
const key = cap as keyof ModelCapabilities;
|
||||||
|
capabilities[key] = clamp(capabilities[key] + value * diminishing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const safetyResearch = state.research.completedResearch.filter(
|
const safetyResearch = state.research.completedResearch.filter(
|
||||||
r => r.includes('alignment') || r.includes('interpretability') || r.includes('constitutional'),
|
r => r.includes('alignment') || r.includes('interpretability') || r.includes('constitutional'),
|
||||||
).length;
|
).length;
|
||||||
const safetyScore = Math.min(100, 30 + safetyResearch * 15 + Math.random() * 10);
|
let overallSafety = Math.min(100, 30 + safetyResearch * 15 + Math.random() * 10);
|
||||||
|
let refusalRate = overallSafety > 60 ? 0.1 : 0.03;
|
||||||
|
|
||||||
const safetyPenalty = safetyScore > 60 ? (safetyScore - 60) * 0.1 : 0;
|
if (pipeline.stages.alignment?.isComplete) {
|
||||||
const benchmarkScore = Math.max(0,
|
completedStages.push('alignment');
|
||||||
(capabilities.reasoning * 0.3 + capabilities.coding * 0.25 +
|
const alignment = pipeline.stages.alignment;
|
||||||
capabilities.creative * 0.2 + capabilities.multimodal * 0.15 + capabilities.agents * 0.1) - safetyPenalty,
|
const methodConfig = ALIGNMENT_METHODS[alignment.method];
|
||||||
);
|
if (methodConfig) {
|
||||||
|
const safetyGain = methodConfig.safetyGain * alignment.safetyWeight;
|
||||||
|
overallSafety = Math.min(100, overallSafety + safetyGain);
|
||||||
|
refusalRate = methodConfig.baseRefusal * Math.pow(alignment.safetyWeight, 1.5);
|
||||||
|
const capLoss = methodConfig.capabilityLoss * alignment.safetyWeight * 0.5;
|
||||||
|
for (const key of Object.keys(capabilities) as (keyof ModelCapabilities)[]) {
|
||||||
|
if (key !== 'speed' && key !== 'contextUtilization') {
|
||||||
|
capabilities[key] = clamp(capabilities[key] - capLoss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const parameterCount = Math.pow(10, generation) * (0.5 + Math.random());
|
const safetyProfile: SafetyProfile = {
|
||||||
|
overallSafety,
|
||||||
|
refusalRate,
|
||||||
|
harmAvoidance: overallSafety,
|
||||||
|
instructionFollowing: capabilities.reasoning * 0.8,
|
||||||
|
honesty: overallSafety * 0.9,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name,
|
familyId: pipeline.familyId,
|
||||||
generation,
|
name: pipeline.modelName,
|
||||||
parameterCount,
|
architecture,
|
||||||
trainingDataSize: dataTokens,
|
dataMix,
|
||||||
capabilities,
|
capabilities,
|
||||||
safetyScore,
|
safetyProfile,
|
||||||
benchmarkScore,
|
rawCapability,
|
||||||
tuning: { preset: 'helpful-safe' },
|
|
||||||
isDeployed: false,
|
isDeployed: false,
|
||||||
trainedAtTick: state.meta.tickCount,
|
trainedAtTick: state.meta.tickCount,
|
||||||
|
trainingCostTotal: compute,
|
||||||
|
trainingStagesCompleted: completedStages,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processVariantJobs(
|
||||||
|
state: GameState,
|
||||||
|
speedMultiplier: number,
|
||||||
|
): { jobs: VariantCreationJob[]; newVariants: ModelVariant[] } {
|
||||||
|
const newVariants: ModelVariant[] = [];
|
||||||
|
const jobs = state.models.variantJobs.map(job => {
|
||||||
|
if (job.status !== 'active') return job;
|
||||||
|
const newProgress = job.progressTicks + speedMultiplier;
|
||||||
|
if (newProgress >= job.totalTicks) {
|
||||||
|
const baseModel = state.models.baseModels.find(m => m.id === job.baseModelId);
|
||||||
|
if (baseModel) {
|
||||||
|
const variant = createVariant(job, baseModel);
|
||||||
|
newVariants.push(variant);
|
||||||
|
}
|
||||||
|
return { ...job, status: 'completed' as const, progressTicks: job.totalTicks };
|
||||||
|
}
|
||||||
|
return { ...job, progressTicks: newProgress };
|
||||||
|
});
|
||||||
|
return { jobs, newVariants };
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVariant(job: VariantCreationJob, base: BaseModel): ModelVariant {
|
||||||
|
const caps = { ...base.capabilities };
|
||||||
|
let costMultiplier = 1.0;
|
||||||
|
let speedMultiplier = 1.0;
|
||||||
|
let variantName = base.name;
|
||||||
|
let arch = { ...base.architecture };
|
||||||
|
|
||||||
|
if (job.jobType === 'distillation' && 'targetParameters' in job.config) {
|
||||||
|
const config = job.config;
|
||||||
|
const sizeRatio = config.targetParameters / base.architecture.totalParameters;
|
||||||
|
const retention = DISTILLATION_BASE_RETENTION + sizeRatio * 0.25;
|
||||||
|
for (const key of Object.keys(caps) as (keyof ModelCapabilities)[]) {
|
||||||
|
caps[key] = clamp(caps[key] * retention);
|
||||||
|
}
|
||||||
|
costMultiplier = sizeRatio * 0.8;
|
||||||
|
speedMultiplier = (1 / sizeRatio) * 0.7;
|
||||||
|
arch = { ...arch, totalParameters: config.targetParameters, activeParameters: config.targetParameters };
|
||||||
|
variantName = config.variantName;
|
||||||
|
} else if (job.jobType === 'fine-tuning' && 'specialization' in job.config) {
|
||||||
|
const config = job.config;
|
||||||
|
const bonuses = SFT_SPECIALIZATION_BONUSES[config.specialization];
|
||||||
|
if (bonuses) {
|
||||||
|
for (const [cap, value] of Object.entries(bonuses)) {
|
||||||
|
caps[cap as keyof ModelCapabilities] = clamp(caps[cap as keyof ModelCapabilities] + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
variantName = config.variantName;
|
||||||
|
} else if (job.jobType === 'quantization' && 'level' in job.config) {
|
||||||
|
const config = job.config;
|
||||||
|
const qConfig = QUANTIZATION_CONFIGS[config.level];
|
||||||
|
if (qConfig) {
|
||||||
|
for (const key of Object.keys(caps) as (keyof ModelCapabilities)[]) {
|
||||||
|
if (key !== 'speed') caps[key] = clamp(caps[key] * qConfig.qualityRetention);
|
||||||
|
}
|
||||||
|
caps.speed = clamp(caps.speed * qConfig.speedMultiplier);
|
||||||
|
costMultiplier = qConfig.costMultiplier;
|
||||||
|
speedMultiplier = qConfig.speedMultiplier;
|
||||||
|
}
|
||||||
|
variantName = config.variantName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: uuid(),
|
||||||
|
familyId: base.familyId,
|
||||||
|
baseModelId: base.id,
|
||||||
|
name: variantName,
|
||||||
|
variantType: job.jobType === 'distillation' ? 'distilled' : job.jobType === 'fine-tuning' ? 'fine-tuned' : 'quantized',
|
||||||
|
architecture: arch,
|
||||||
|
capabilities: caps,
|
||||||
|
safetyProfile: { ...base.safetyProfile },
|
||||||
|
isDeployed: false,
|
||||||
|
createdAtTick: 0,
|
||||||
|
quantization: job.jobType === 'quantization' && 'level' in job.config ? job.config.level : undefined,
|
||||||
|
distillationRetention: job.jobType === 'distillation' && 'targetParameters' in job.config
|
||||||
|
? DISTILLATION_BASE_RETENTION + (job.config.targetParameters / base.architecture.totalParameters) * 0.25
|
||||||
|
: undefined,
|
||||||
|
finetuneSpecialization: job.jobType === 'fine-tuning' && 'specialization' in job.config ? job.config.specialization : undefined,
|
||||||
|
costMultiplier,
|
||||||
|
speedMultiplier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function processEvalJobs(state: GameState): { jobs: EvalJob[]; newResults: BenchmarkResult[] } {
|
||||||
|
const newResults: BenchmarkResult[] = [];
|
||||||
|
const allModels: (BaseModel | ModelVariant)[] = [
|
||||||
|
...state.models.baseModels,
|
||||||
|
...state.models.families.flatMap(f => f.variants),
|
||||||
|
];
|
||||||
|
|
||||||
|
const jobs = state.models.evalJobs.map(job => {
|
||||||
|
if (job.status !== 'active') return job;
|
||||||
|
const newProgress = job.progressTicks + 1;
|
||||||
|
if (newProgress >= job.totalTicks) {
|
||||||
|
const model = allModels.find(m => m.id === job.modelId);
|
||||||
|
if (model) {
|
||||||
|
const results = computeBenchmarkScores(model, job.benchmarkIds, state.meta.tickCount);
|
||||||
|
newResults.push(...results);
|
||||||
|
return { ...job, status: 'completed' as const, progressTicks: job.totalTicks, results };
|
||||||
|
}
|
||||||
|
return { ...job, status: 'completed' as const, progressTicks: job.totalTicks };
|
||||||
|
}
|
||||||
|
return { ...job, progressTicks: newProgress };
|
||||||
|
});
|
||||||
|
return { jobs, newResults };
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeBenchmarkScores(
|
||||||
|
model: BaseModel | ModelVariant,
|
||||||
|
benchmarkIds: string[],
|
||||||
|
tick: number,
|
||||||
|
): BenchmarkResult[] {
|
||||||
|
const benchmarkMap = new Map(BENCHMARKS.map(b => [b.id, b]));
|
||||||
|
return benchmarkIds.map(id => {
|
||||||
|
const bench = benchmarkMap.get(id);
|
||||||
|
if (!bench) return { benchmarkId: id, modelId: model.id, score: 0, ranAtTick: tick };
|
||||||
|
const primary = model.capabilities[bench.primaryCapability] ?? 0;
|
||||||
|
const secondary = bench.secondaryCapability ? (model.capabilities[bench.secondaryCapability] ?? 0) : 0;
|
||||||
|
const noise = (Math.random() - 0.5) * 6;
|
||||||
|
const score = clamp(primary * 0.7 + secondary * 0.3 + noise);
|
||||||
|
return { benchmarkId: id, modelId: model.id, score, ranAtTick: tick };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeVariantScore(variant: ModelVariant): number {
|
||||||
|
const c = variant.capabilities;
|
||||||
|
return (c.reasoning * 0.25 + c.coding * 0.2 + c.creative * 0.15 + c.math * 0.15 + c.knowledge * 0.15 + c.agents * 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
function clamp(n: number): number {
|
function clamp(n: number): number {
|
||||||
return Math.min(100, Math.max(0, n));
|
return Math.min(100, Math.max(0, n));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,9 @@ export interface ReputationTickResult {
|
|||||||
export function processReputation(state: GameState): ReputationState & { _safetyIncident?: boolean } {
|
export function processReputation(state: GameState): ReputationState & { _safetyIncident?: boolean } {
|
||||||
let { safetyRecord, publicPerception, employeeSatisfaction, regulatoryStanding } = state.reputation;
|
let { safetyRecord, publicPerception, employeeSatisfaction, regulatoryStanding } = state.reputation;
|
||||||
|
|
||||||
const bestModel = state.models.trainedModels
|
|
||||||
.filter(m => m.isDeployed)
|
|
||||||
.sort((a, b) => b.benchmarkScore - a.benchmarkScore)[0];
|
|
||||||
|
|
||||||
let safetyIncident = false;
|
let safetyIncident = false;
|
||||||
if (bestModel) {
|
if (state.models.bestDeployedSafetyScore > 0) {
|
||||||
const safetyLevel = bestModel.safetyScore;
|
const safetyLevel = state.models.bestDeployedSafetyScore;
|
||||||
if (safetyLevel < LOW_SAFETY_THRESHOLD && state.meta.tickCount % 60 === 0) {
|
if (safetyLevel < LOW_SAFETY_THRESHOLD && state.meta.tickCount % 60 === 0) {
|
||||||
const incidentProb = SAFETY_INCIDENT_PROBABILITY_BASE * (LOW_SAFETY_THRESHOLD - safetyLevel);
|
const incidentProb = SAFETY_INCIDENT_PROBABILITY_BASE * (LOW_SAFETY_THRESHOLD - safetyLevel);
|
||||||
if (Math.random() < incidentProb) {
|
if (Math.random() < incidentProb) {
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ export function processTick(state: GameState): Partial<GameState> {
|
|||||||
const stateWithInfra = { ...state, infrastructure };
|
const stateWithInfra = { ...state, infrastructure };
|
||||||
const modelResult = processModels(stateWithInfra);
|
const modelResult = processModels(stateWithInfra);
|
||||||
|
|
||||||
if (modelResult.modelCompleted) {
|
for (const completed of modelResult.completedModels) {
|
||||||
notifications.push({
|
notifications.push({
|
||||||
title: 'Training Complete',
|
title: 'Training Complete',
|
||||||
message: `${modelResult.modelCompleted.name} is ready! Benchmark: ${modelResult.modelCompleted.benchmarkScore.toFixed(1)}/100`,
|
message: `${completed.name} is ready! Capability: ${completed.rawCapability.toFixed(1)}/100`,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
notifications.push(...modelResult.notifications);
|
||||||
|
|
||||||
const stateWithModels = { ...stateWithInfra, models: modelResult.modelsState };
|
const stateWithModels = { ...stateWithInfra, models: modelResult.modelsState };
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,66 @@ export const CAPABILITY_FORMULA = {
|
|||||||
efficiencyWeight: 0.1,
|
efficiencyWeight: 0.1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PRETRAINING_BASE_TICKS = 180;
|
||||||
|
export const SFT_TIME_FRACTION = 0.10;
|
||||||
|
export const SFT_COMPUTE_FRACTION = 0.06;
|
||||||
|
export const ALIGNMENT_TIME_FRACTION = 0.08;
|
||||||
|
export const ALIGNMENT_COMPUTE_FRACTION = 0.04;
|
||||||
|
export const CHINCHILLA_OPTIMAL_RATIO = 20;
|
||||||
|
|
||||||
|
export const MAX_CONCURRENT_TRAINING: Record<string, number> = {
|
||||||
|
startup: 1, scaleup: 2, bigtech: 4, agi: 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DISTILLATION_COMPUTE_FRACTION = 0.15;
|
||||||
|
export const DISTILLATION_TIME_FRACTION = 0.20;
|
||||||
|
export const DISTILLATION_BASE_RETENTION = 0.70;
|
||||||
|
export const FINETUNE_COMPUTE_FRACTION = 0.03;
|
||||||
|
export const FINETUNE_TIME_FRACTION = 0.08;
|
||||||
|
export const QUANTIZATION_TICKS = 8;
|
||||||
|
|
||||||
|
export const MOE_CAPABILITY_MULTIPLIER = 1.15;
|
||||||
|
export const MOE_SPEED_MULTIPLIER = 1.3;
|
||||||
|
export const PARAMETER_OPTIONS = [1, 3, 7, 13, 30, 70, 130, 300, 700, 1400];
|
||||||
|
export const CONTEXT_WINDOW_OPTIONS = [4, 8, 32, 128, 256, 1024];
|
||||||
|
|
||||||
|
export const EVENT_BASE_PROBABILITY = 0.001;
|
||||||
|
export const LOSS_SPIKE_DELAY_MIN = 5;
|
||||||
|
export const LOSS_SPIKE_DELAY_MAX = 20;
|
||||||
|
export const INSTABILITY_PROGRESS_LOSS_MIN = 0.05;
|
||||||
|
export const INSTABILITY_PROGRESS_LOSS_MAX = 0.15;
|
||||||
|
export const BREAKTHROUGH_CAPABILITY_BONUS_MIN = 5;
|
||||||
|
export const BREAKTHROUGH_CAPABILITY_BONUS_MAX = 15;
|
||||||
|
|
||||||
|
export const EMERGENT_CAPABILITY_THRESHOLDS: Record<number, string> = {
|
||||||
|
10: 'basic-reasoning',
|
||||||
|
50: 'chain-of-thought',
|
||||||
|
100: 'tool-use',
|
||||||
|
500: 'long-horizon-planning',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QUANTIZATION_CONFIGS: Record<string, { qualityRetention: number; speedMultiplier: number; costMultiplier: number }> = {
|
||||||
|
fp16: { qualityRetention: 1.00, speedMultiplier: 1.0, costMultiplier: 1.0 },
|
||||||
|
int8: { qualityRetention: 0.97, speedMultiplier: 1.8, costMultiplier: 0.55 },
|
||||||
|
int4: { qualityRetention: 0.90, speedMultiplier: 3.0, costMultiplier: 0.30 },
|
||||||
|
int2: { qualityRetention: 0.75, speedMultiplier: 5.0, costMultiplier: 0.15 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ALIGNMENT_METHODS: Record<string, { safetyGain: number; capabilityLoss: number; baseRefusal: number; requiredResearch: string }> = {
|
||||||
|
rlhf: { safetyGain: 25, capabilityLoss: 5, baseRefusal: 0.10, requiredResearch: 'alignment-research' },
|
||||||
|
dpo: { safetyGain: 20, capabilityLoss: 2, baseRefusal: 0.05, requiredResearch: 'interpretability' },
|
||||||
|
constitutional: { safetyGain: 30, capabilityLoss: 4, baseRefusal: 0.14, requiredResearch: 'constitutional-ai' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SFT_SPECIALIZATION_BONUSES: Record<string, Record<string, number>> = {
|
||||||
|
general: { reasoning: 5, coding: 5, creative: 5, math: 5, knowledge: 5, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0 },
|
||||||
|
code: { reasoning: 0, coding: 15, creative: -3, math: 8, knowledge: 0, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0 },
|
||||||
|
math: { reasoning: 8, coding: 0, creative: -3, math: 15, knowledge: 0, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0 },
|
||||||
|
creative: { reasoning: 0, coding: -3, creative: 15, math: 0, knowledge: 5, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0 },
|
||||||
|
multilingual: { reasoning: 0, coding: 0, creative: 0, math: 0, knowledge: 10, multimodal: 0, agents: 0, speed: -5, contextUtilization: 0 },
|
||||||
|
'tool-use': { reasoning: 0, coding: 8, creative: 0, math: 0, knowledge: 0, multimodal: 0, agents: 15, speed: -5, contextUtilization: 0 },
|
||||||
|
};
|
||||||
|
|
||||||
export const CONSUMER_BASE_GROWTH = 0.002;
|
export const CONSUMER_BASE_GROWTH = 0.002;
|
||||||
export const CONSUMER_QUALITY_GROWTH_MULTIPLIER = 0.01;
|
export const CONSUMER_QUALITY_GROWTH_MULTIPLIER = 0.01;
|
||||||
export const CONSUMER_PRICE_ELASTICITY = -0.5;
|
export const CONSUMER_PRICE_ELASTICITY = -0.5;
|
||||||
|
|||||||
@@ -58,4 +58,4 @@ export const INITIAL_SETTINGS: GameSettings = {
|
|||||||
sfxVolume: 0.7,
|
sfxVolume: 0.7,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SAVE_VERSION = 5;
|
export const SAVE_VERSION = 6;
|
||||||
|
|||||||
@@ -1,51 +1,239 @@
|
|||||||
export interface ModelsState {
|
import type { Era } from './gameState';
|
||||||
trainedModels: TrainedModel[];
|
import type { DataDomain } from './data';
|
||||||
activeTraining: TrainingJob | null;
|
|
||||||
productLines: ProductLine[];
|
export type ArchitectureType = 'dense' | 'moe';
|
||||||
|
|
||||||
|
export interface ModelArchitecture {
|
||||||
|
type: ArchitectureType;
|
||||||
|
totalParameters: number;
|
||||||
|
activeParameters: number;
|
||||||
|
expertCount?: number;
|
||||||
|
expertTopK?: number;
|
||||||
|
contextWindow: number;
|
||||||
|
vocabularySize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrainedModel {
|
export type DataMixAllocation = Record<DataDomain, number>;
|
||||||
|
|
||||||
|
export type TrainingStage = 'pretraining' | 'sft' | 'alignment';
|
||||||
|
export type TrainingJobStatus = 'active' | 'paused' | 'stalled' | 'completed' | 'failed';
|
||||||
|
|
||||||
|
export interface TrainingPipeline {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
familyId: string;
|
||||||
generation: number;
|
modelName: string;
|
||||||
parameterCount: number;
|
architecture: ModelArchitecture;
|
||||||
trainingDataSize: number;
|
dataMix: DataMixAllocation;
|
||||||
capabilities: ModelCapabilities;
|
currentStage: TrainingStage;
|
||||||
safetyScore: number;
|
stages: {
|
||||||
benchmarkScore: number;
|
pretraining: PreTrainingConfig;
|
||||||
tuning: ModelTuning;
|
sft: SFTConfig | null;
|
||||||
isDeployed: boolean;
|
alignment: AlignmentConfig | null;
|
||||||
trainedAtTick: number;
|
};
|
||||||
|
status: TrainingJobStatus;
|
||||||
|
allocatedComputeFraction: number;
|
||||||
|
events: TrainingEvent[];
|
||||||
|
startedAtTick: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PreTrainingConfig {
|
||||||
|
targetTokens: number;
|
||||||
|
processedTokens: number;
|
||||||
|
computeAllocated: number;
|
||||||
|
progressTicks: number;
|
||||||
|
totalTicks: number;
|
||||||
|
lossValue: number;
|
||||||
|
chinchillaRatio: number;
|
||||||
|
isComplete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SFTSpecialization = 'general' | 'code' | 'math' | 'creative' | 'multilingual' | 'tool-use';
|
||||||
|
|
||||||
|
export interface SFTConfig {
|
||||||
|
specializations: SFTSpecialization[];
|
||||||
|
progressTicks: number;
|
||||||
|
totalTicks: number;
|
||||||
|
isComplete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AlignmentMethod = 'rlhf' | 'dpo' | 'constitutional';
|
||||||
|
|
||||||
|
export interface AlignmentConfig {
|
||||||
|
method: AlignmentMethod;
|
||||||
|
safetyWeight: number;
|
||||||
|
helpfulnessWeight: number;
|
||||||
|
progressTicks: number;
|
||||||
|
totalTicks: number;
|
||||||
|
isComplete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TrainingEventType =
|
||||||
|
| 'loss_spike'
|
||||||
|
| 'instability'
|
||||||
|
| 'breakthrough'
|
||||||
|
| 'emergent_capability'
|
||||||
|
| 'data_contamination'
|
||||||
|
| 'hardware_failure';
|
||||||
|
|
||||||
|
export interface TrainingEvent {
|
||||||
|
id: string;
|
||||||
|
type: TrainingEventType;
|
||||||
|
tick: number;
|
||||||
|
severity: 'minor' | 'moderate' | 'major';
|
||||||
|
description: string;
|
||||||
|
resolved: boolean;
|
||||||
|
impact: {
|
||||||
|
ticksDelayed?: number;
|
||||||
|
progressLost?: number;
|
||||||
|
capabilityBonus?: number;
|
||||||
|
capabilityDomain?: keyof ModelCapabilities;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelCapabilities {
|
export interface ModelCapabilities {
|
||||||
reasoning: number;
|
reasoning: number;
|
||||||
coding: number;
|
coding: number;
|
||||||
creative: number;
|
creative: number;
|
||||||
|
math: number;
|
||||||
|
knowledge: number;
|
||||||
multimodal: number;
|
multimodal: number;
|
||||||
agents: number;
|
agents: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
|
contextUtilization: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelTuning {
|
export interface SafetyProfile {
|
||||||
preset: TuningPreset;
|
overallSafety: number;
|
||||||
verbosity?: number;
|
refusalRate: number;
|
||||||
safetyLevel?: number;
|
harmAvoidance: number;
|
||||||
creativity?: number;
|
instructionFollowing: number;
|
||||||
speedQuality?: number;
|
honesty: number;
|
||||||
refusalRate?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TuningPreset = 'helpful-safe' | 'max-capability' | 'enterprise' | 'creative';
|
export interface BaseModel {
|
||||||
|
id: string;
|
||||||
|
familyId: string;
|
||||||
|
name: string;
|
||||||
|
architecture: ModelArchitecture;
|
||||||
|
dataMix: DataMixAllocation;
|
||||||
|
capabilities: ModelCapabilities;
|
||||||
|
safetyProfile: SafetyProfile;
|
||||||
|
rawCapability: number;
|
||||||
|
isDeployed: boolean;
|
||||||
|
trainedAtTick: number;
|
||||||
|
trainingCostTotal: number;
|
||||||
|
trainingStagesCompleted: TrainingStage[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface TrainingJob {
|
export type VariantType = 'distilled' | 'fine-tuned' | 'quantized';
|
||||||
modelName: string;
|
export type QuantizationLevel = 'fp16' | 'int8' | 'int4' | 'int2';
|
||||||
|
|
||||||
|
export interface ModelVariant {
|
||||||
|
id: string;
|
||||||
|
familyId: string;
|
||||||
|
baseModelId: string;
|
||||||
|
name: string;
|
||||||
|
variantType: VariantType;
|
||||||
|
architecture: ModelArchitecture;
|
||||||
|
capabilities: ModelCapabilities;
|
||||||
|
safetyProfile: SafetyProfile;
|
||||||
|
isDeployed: boolean;
|
||||||
|
createdAtTick: number;
|
||||||
|
quantization?: QuantizationLevel;
|
||||||
|
distillationRetention?: number;
|
||||||
|
finetuneSpecialization?: SFTSpecialization;
|
||||||
|
costMultiplier: number;
|
||||||
|
speedMultiplier: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModelFamily {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
generation: number;
|
generation: number;
|
||||||
allocatedCompute: number;
|
baseModelId: string | null;
|
||||||
allocatedDataTokens: number;
|
variants: ModelVariant[];
|
||||||
|
createdAtTick: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VariantJobType = 'distillation' | 'fine-tuning' | 'quantization';
|
||||||
|
|
||||||
|
export interface VariantCreationJob {
|
||||||
|
id: string;
|
||||||
|
familyId: string;
|
||||||
|
baseModelId: string;
|
||||||
|
jobType: VariantJobType;
|
||||||
|
config: DistillationConfig | FineTuneConfig | QuantizationConfig;
|
||||||
progressTicks: number;
|
progressTicks: number;
|
||||||
totalTicks: number;
|
totalTicks: number;
|
||||||
estimatedCapability: number;
|
allocatedComputeFraction: number;
|
||||||
|
status: 'active' | 'completed';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistillationConfig {
|
||||||
|
targetParameters: number;
|
||||||
|
targetArchitecture: ArchitectureType;
|
||||||
|
variantName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FineTuneConfig {
|
||||||
|
specialization: SFTSpecialization;
|
||||||
|
datasetIds: string[];
|
||||||
|
variantName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuantizationConfig {
|
||||||
|
level: QuantizationLevel;
|
||||||
|
variantName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BenchmarkCategory = 'reasoning' | 'coding' | 'math' | 'knowledge' | 'safety' | 'chat' | 'multimodal' | 'agents';
|
||||||
|
|
||||||
|
export interface BenchmarkDefinition {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
category: BenchmarkCategory;
|
||||||
|
description: string;
|
||||||
|
primaryCapability: keyof ModelCapabilities;
|
||||||
|
secondaryCapability?: keyof ModelCapabilities;
|
||||||
|
computeCost: number;
|
||||||
|
ticksToRun: number;
|
||||||
|
unlockedAtEra: Era;
|
||||||
|
marketRelevance: {
|
||||||
|
consumer: number;
|
||||||
|
enterprise: number;
|
||||||
|
developer: number;
|
||||||
|
research: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BenchmarkResult {
|
||||||
|
benchmarkId: string;
|
||||||
|
modelId: string;
|
||||||
|
score: number;
|
||||||
|
ranAtTick: number;
|
||||||
|
rank?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EvalJob {
|
||||||
|
id: string;
|
||||||
|
modelId: string;
|
||||||
|
benchmarkIds: string[];
|
||||||
|
progressTicks: number;
|
||||||
|
totalTicks: number;
|
||||||
|
computeAllocated: number;
|
||||||
|
status: 'active' | 'completed';
|
||||||
|
results: BenchmarkResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductLineType = 'text-api' | 'chat-product' | 'chat-free' | 'chat-enterprise' | 'code-api' | 'image' | 'agents-api';
|
||||||
|
|
||||||
|
export interface ProductPricing {
|
||||||
|
inputTokenPrice: number;
|
||||||
|
outputTokenPrice: number;
|
||||||
|
thinkingTokenBudget: number;
|
||||||
|
cachingEnabled: boolean;
|
||||||
|
subscriptionPrice: number;
|
||||||
|
freeTokenAllowance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductLine {
|
export interface ProductLine {
|
||||||
@@ -57,20 +245,38 @@ export interface ProductLine {
|
|||||||
pricing: ProductPricing;
|
pricing: ProductPricing;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductLineType = 'text-api' | 'chat-product' | 'image' | 'code' | 'agents';
|
export interface ModelsState {
|
||||||
|
families: ModelFamily[];
|
||||||
export interface ProductPricing {
|
baseModels: BaseModel[];
|
||||||
inputTokenPrice: number;
|
activeTrainingPipelines: TrainingPipeline[];
|
||||||
outputTokenPrice: number;
|
variantJobs: VariantCreationJob[];
|
||||||
thinkingTokenBudget: number;
|
evalJobs: EvalJob[];
|
||||||
cachingEnabled: boolean;
|
benchmarkResults: BenchmarkResult[];
|
||||||
subscriptionPrice: number;
|
productLines: ProductLine[];
|
||||||
freeTokenAllowance: number;
|
bestDeployedModelScore: number;
|
||||||
|
bestDeployedSafetyScore: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_DATA_MIX: DataMixAllocation = {
|
||||||
|
web: 0.35,
|
||||||
|
books: 0.10,
|
||||||
|
code: 0.15,
|
||||||
|
scientific: 0.10,
|
||||||
|
conversation: 0.10,
|
||||||
|
multilingual: 0.05,
|
||||||
|
images: 0.05,
|
||||||
|
video: 0.03,
|
||||||
|
audio: 0.02,
|
||||||
|
synthetic: 0.05,
|
||||||
|
};
|
||||||
|
|
||||||
export const INITIAL_MODELS: ModelsState = {
|
export const INITIAL_MODELS: ModelsState = {
|
||||||
trainedModels: [],
|
families: [],
|
||||||
activeTraining: null,
|
baseModels: [],
|
||||||
|
activeTrainingPipelines: [],
|
||||||
|
variantJobs: [],
|
||||||
|
evalJobs: [],
|
||||||
|
benchmarkResults: [],
|
||||||
productLines: [
|
productLines: [
|
||||||
{
|
{
|
||||||
id: 'text-api',
|
id: 'text-api',
|
||||||
@@ -103,4 +309,6 @@ export const INITIAL_MODELS: ModelsState = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
bestDeployedModelScore: 0,
|
||||||
|
bestDeployedSafetyScore: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user