Spaces:
Running on Zero
Running on Zero
File size: 4,780 Bytes
a6d3251 6c60ea0 a6d3251 e70da5c 46ae7f5 a6d3251 46ae7f5 2e4c103 a6d3251 a948328 2e4c103 a6d3251 2e4c103 a6d3251 6c60ea0 a948328 a6d3251 46ae7f5 6c60ea0 a6d3251 2e4c103 a6d3251 6c60ea0 46ae7f5 2e4c103 a6d3251 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | """Settings contract shared by the CPU and leased GPU dispatch paths."""
import copy
import math
DEFAULTS={'mode':'train','clean_min_laps':2,'population':4,'generations':1,'ticks':60,'mutation':.01,'seed':42,
'mutation_end':.01,'schedule':'fixed','selection':'best','parents':1,'elite':1,
'crossover':'none','decay':0,'adaptive_mutation':False,'control_hz':60,'workers':1,
'collision':False,'idle':False,'freeze':{'layers':[],'inputs':[],'outputs':[]},
'rewards':[{'metric':'total_score','type':'default','weight':100}]}
METRICS={'total_score','total_speed','total_abs_steering','total_actual_distance',
'total_drifting_amount','total_momentum_change_frequency','total_throttle_change_frequency',
'total_braking_frequency','total_distance_from_wall','total_distance_from_center',
'network_novelty','collision_count','best_lap_performance','total_racing_line_efficiency'}
def validate_freeze(value):
"""Check the settings envelope; the uploaded network supplies exact bounds."""
if not isinstance(value,dict) or set(value)!={'layers','inputs','outputs'}:
raise ValueError('Invalid freeze selection')
result={}
for key,low,high in (('layers',1,10),('inputs',0,19),('outputs',0,4)):
items=value[key]
if (not isinstance(items,list) or len(items)>high-low+1 or
any(type(item) is not int or not low<=item<=high for item in items) or
items!=sorted(set(items))):
raise ValueError('Invalid frozen '+key)
result[key]=items[:]
return result
def validate_config(value=None, *, work_limit=4_000_000):
if value is None:return copy.deepcopy(DEFAULTS)
if not isinstance(value,dict) or set(value)-set(DEFAULTS):raise ValueError('Unexpected training settings')
c={**copy.deepcopy(DEFAULTS),**value}
if type(c['control_hz']) is not int or c['control_hz']!=60:
raise ValueError('New training and evaluation runs require control_hz=60; historical records are not converted')
bounds={'population':(1,1024,True),'generations':(1,2000,True),'ticks':(1,18000,True),
'mutation':(0,4.2,False),'mutation_end':(0,4.2,False),'seed':(0,2147483647,True),
'clean_min_laps':(1,100,True),'parents':(1,1024,True),'elite':(0,1024,True),'decay':(0,1,False),'workers':(1,4,True)}
for key,(low,high,integer) in bounds.items():
v=c[key]
if type(v) not in (int,float) or not math.isfinite(v) or not low<=v<=high or (integer and type(v) is not int):raise ValueError('Invalid '+key)
for key,choices in {'mode':('train','evaluate'),'schedule':('fixed','linear','geometric'),'selection':('best','tournament','roulette'),
'crossover':('none','single_point','uniform'),'control_hz':(60,)}.items():
if c[key] not in choices or (key=='control_hz' and type(c[key]) is not int):raise ValueError('Invalid '+key)
for key in ('adaptive_mutation','collision','idle'):
if type(c[key]) is not bool:raise ValueError('Invalid '+key)
c['freeze']=validate_freeze(c['freeze'])
if c['mode']=='train' and (c['parents']>c['population'] or c['elite']>c['population']):raise ValueError('Parents/elites exceed population')
if c['mode']=='train' and (c['mutation']<=0 or (c['schedule']!='fixed' and c['mutation_end']<=0)):raise ValueError('This training runner requires positive mutation rates, including when continuing an uploaded network')
# Echo the rate actually passed to the native CLI, not an unused form value.
if c['schedule']=='fixed':c['mutation_end']=c['mutation']
rewards=c['rewards']
if not isinstance(rewards,list) or not 1<=len(rewards)<=len(METRICS):raise ValueError('Use one or more distinct supported rewards')
seen=set();clean=[]
for r in rewards:
if not isinstance(r,dict) or set(r)!= {'metric','type','weight'}:raise ValueError('Unexpected reward fields')
if not isinstance(r['metric'],str) or r['metric'] not in METRICS or r['type'] not in ('default','average') or r['metric'] in seen or type(r['weight']) is not int or abs(r['weight'])>100:raise ValueError('Invalid reward')
seen.add(r['metric']);clean.append(dict(r))
if not any(r['weight'] for r in clean):raise ValueError('A reward weight must be nonzero')
c['rewards']=clean
if c['mode']=='evaluate':
c.update(population=1,generations=1,mutation=0,mutation_end=0,decay=0,adaptive_mutation=False,parents=1,elite=0,crossover='none',selection='best',schedule='fixed',freeze={'layers':[],'inputs':[],'outputs':[]})
if c['population']*c['generations']>32_768:raise ValueError('Run exceeds 32,768 car-generation analysis rows')
if c['population']*c['generations']*c['ticks']>work_limit:raise ValueError('Run exceeds backend vehicle-tick limit')
return c
|