test: admit audited shifted N64 space-time trajectory
This commit is contained in:
1 parent
e8b8c58995
commit
26d0d3853c
2 files changed
+74
-15
No files matched your search
@@ -22,7 +22,7 @@ ref = gpu.ref
|
|||||||
|
|
||||||
CPU_SHA = '180eef90a1c3f43113fe18cc54981ee4723cfc0cc2724334907ff188022fa770'
|
CPU_SHA = '180eef90a1c3f43113fe18cc54981ee4723cfc0cc2724334907ff188022fa770'
|
||||||
UNIFORM_SHA = '01e7415ae39456f2de7f0c3d331afb971c49d19353b43038650e32e80737f1e7'
|
UNIFORM_SHA = '01e7415ae39456f2de7f0c3d331afb971c49d19353b43038650e32e80737f1e7'
|
||||||
CUDA_SHA = 'c47869688f7ac2ec93411c0af233887fe8b77405838299e4ae52c8963fd97fe4'
|
CUDA_SHA = 'afea11ea4a1c2c3800b2f6a7a8e81f5568989fc9e1cc02fb52d7e1ea3864e003'
|
||||||
AUDIT_SHA = 'fba340b1533bec308b666ee84666df0ac9d08e2872be31ee379e2d1447565158'
|
AUDIT_SHA = 'fba340b1533bec308b666ee84666df0ac9d08e2872be31ee379e2d1447565158'
|
||||||
STEPS = (256, 512, 768, 1024)
|
STEPS = (256, 512, 768, 1024)
|
||||||
TIMES = (.12, .24, .36, .48)
|
TIMES = (.12, .24, .36, .48)
|
||||||
@@ -66,6 +66,12 @@ def differing_fields(actual, expected):
|
|||||||
or np.asarray(actual[key]).tobytes() != np.asarray(expected[key]).tobytes()]
|
or np.asarray(actual[key]).tobytes() != np.asarray(expected[key]).tobytes()]
|
||||||
|
|
||||||
|
|
||||||
|
def sample_difference(actual, expected):
|
||||||
|
"""Descriptive comparison only; never an equivalence gate for different codecs."""
|
||||||
|
return {key:dict(reference=value,signed=(np.asarray(actual[key])-value).tolist(),
|
||||||
|
absolute=np.abs(np.asarray(actual[key])-value).tolist()) for key,value in expected.items()}
|
||||||
|
|
||||||
|
|
||||||
class LedgerTotals:
|
class LedgerTotals:
|
||||||
"""Compensated directional sums; retain absolute step sums to expose cancellation."""
|
"""Compensated directional sums; retain absolute step sums to expose cancellation."""
|
||||||
names = ('net_change','physical_collision','coarse_collision','fine0_collision','fine1_collision',
|
names = ('net_change','physical_collision','coarse_collision','fine0_collision','fine1_collision',
|
||||||
@@ -184,18 +190,24 @@ def main():
|
|||||||
parser.add_argument('--cpu-receipt', type=Path, required=True)
|
parser.add_argument('--cpu-receipt', type=Path, required=True)
|
||||||
parser.add_argument('--uniform-receipt', type=Path, required=True)
|
parser.add_argument('--uniform-receipt', type=Path, required=True)
|
||||||
parser.add_argument('--audit-against', type=Path, help='audit every interval and compare exact saved 1779cf2 samples')
|
parser.add_argument('--audit-against', type=Path, help='audit every interval and compare exact saved 1779cf2 samples')
|
||||||
|
parser.add_argument('--shifted', action='store_true', help='opt-in shifted storage with all interval ledgers')
|
||||||
|
parser.add_argument('--unshifted-receipt', type=Path, help='pinned1779 comparison, descriptive only')
|
||||||
parser.add_argument('--output', type=Path, required=True)
|
parser.add_argument('--output', type=Path, required=True)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if not args.gpu_admitted or not os.environ.get('CUDA_VISIBLE_DEVICES'):
|
if not args.gpu_admitted or not os.environ.get('CUDA_VISIBLE_DEVICES'):
|
||||||
parser.error('explicit --gpu-admitted and CUDA_VISIBLE_DEVICES required')
|
parser.error('explicit --gpu-admitted and CUDA_VISIBLE_DEVICES required')
|
||||||
if args.output.exists():
|
if args.output.exists():
|
||||||
parser.error('output exists; preserve evidence and choose a new path')
|
parser.error('output exists; preserve evidence and choose a new path')
|
||||||
|
if args.shifted != (args.unshifted_receipt is not None) or (args.shifted and args.audit_against):
|
||||||
|
parser.error('--shifted requires --unshifted-receipt and cannot use exact --audit-against')
|
||||||
|
exact_repeat = args.audit_against is not None
|
||||||
cpu, uniform = load_references(args.cpu_receipt, args.uniform_receipt)
|
cpu, uniform = load_references(args.cpu_receipt, args.uniform_receipt)
|
||||||
previous = None
|
previous = None
|
||||||
if args.audit_against is not None:
|
comparison_path = args.audit_against or args.unshifted_receipt
|
||||||
if sha(args.audit_against) != AUDIT_SHA:
|
if comparison_path is not None:
|
||||||
|
if sha(comparison_path) != AUDIT_SHA:
|
||||||
raise ValueError('pinned GPU trajectory receipt differs')
|
raise ValueError('pinned GPU trajectory receipt differs')
|
||||||
previous = json.loads(args.audit_against.read_text())
|
previous = json.loads(comparison_path.read_text())
|
||||||
if previous['source']['numpy'] != np.__version__:
|
if previous['source']['numpy'] != np.__version__:
|
||||||
raise RuntimeError('audited repeat requires unchanged NumPy environment')
|
raise RuntimeError('audited repeat requires unchanged NumPy environment')
|
||||||
gpu.check_sources()
|
gpu.check_sources()
|
||||||
@@ -212,8 +224,9 @@ def main():
|
|||||||
report = dict(scope='N64 experimental space-time GPU; no grid-convergence qualification',
|
report = dict(scope='N64 experimental space-time GPU; no grid-convergence qualification',
|
||||||
completed=False, passed=False, source=source, rows=[], health_counts=[0]*6, health_labels=HEALTH,
|
completed=False, passed=False, source=source, rows=[], health_counts=[0]*6, health_labels=HEALTH,
|
||||||
setup=dict(n=64, bounds=[8,8,56,48], re_L=100, U=.03, viscosity=.0192, tau=[.5576,.6152],
|
setup=dict(n=64, bounds=[8,8,56,48], re_L=100, U=.03, viscosity=.0192, tau=[.5576,.6152],
|
||||||
phase='arrival_f', storage='unshifted_FP32', collision='SRT', interval_count=1024,
|
phase='arrival_f', storage='shifted_FP32' if args.shifted else 'unshifted_FP32', collision='SRT', interval_count=1024,
|
||||||
initialization='same physical FP64 equilibrium as CPU, rounded to FP32 on upload',
|
initialization='common analytic FP64 field, encoded using production FP64 weights before FP32 upload' if args.shifted
|
||||||
|
else 'same physical FP64 equilibrium as CPU, rounded to FP32 on upload',
|
||||||
cpu_storage='FP64', sample_steps=STEPS, sample_times=TIMES),
|
cpu_storage='FP64', sample_steps=STEPS, sample_times=TIMES),
|
||||||
references=dict(cpu_receipt_sha256=CPU_SHA, cpu_source=cpu['source'],
|
references=dict(cpu_receipt_sha256=CPU_SHA, cpu_source=cpu['source'],
|
||||||
cpu_archive_drive_id='197F9nwDOGf_YROK1a46YpVvNrSldnH_w',
|
cpu_archive_drive_id='197F9nwDOGf_YROK1a46YpVvNrSldnH_w',
|
||||||
@@ -230,7 +243,8 @@ def main():
|
|||||||
if previous:
|
if previous:
|
||||||
report['diagnostic'] = dict(completed=False, passed=False, reference_sha256=AUDIT_SHA,
|
report['diagnostic'] = dict(completed=False, passed=False, reference_sha256=AUDIT_SHA,
|
||||||
reference_source=previous['source']['commit'], sample_checks=[], prefixes=[],
|
reference_source=previous['source']['commit'], sample_checks=[], prefixes=[],
|
||||||
scope='net drift attribution; exact saved diagnostics only, not full-field trajectory certification')
|
scope='net drift attribution; exact saved diagnostics only, not full-field trajectory certification' if exact_repeat
|
||||||
|
else 'raw stored-value ledger; unshifted and CPU comparisons descriptive, initial codecs differ')
|
||||||
phase = 'setup'
|
phase = 'setup'
|
||||||
|
|
||||||
def save():
|
def save():
|
||||||
@@ -256,16 +270,17 @@ def main():
|
|||||||
try:
|
try:
|
||||||
from CelerisLab.cuda.context import CudaContext
|
from CelerisLab.cuda.context import CudaContext
|
||||||
context = CudaContext(0)
|
context = CudaContext(0)
|
||||||
config = LBMConfig(nx=64, ny=64, viscosity=.0192, velocity=0., ddf_shifting=False)
|
config = LBMConfig(nx=64, ny=64, viscosity=.0192, velocity=0., ddf_shifting=args.shifted)
|
||||||
run = gpu.SpaceTimeGPU(config, (8,8,56,48), context=context)
|
run = gpu.SpaceTimeGPU(config, (8,8,56,48), context=context)
|
||||||
report.update(device=context.name, sm_arch=context.sm_arch, build_metadata=run.build_metadata,
|
report.update(device=context.name, sm_arch=context.sm_arch, build_metadata=run.build_metadata,
|
||||||
table_sha256=gpu.table_hash(run.tables))
|
table_sha256=gpu.table_hash(run.tables))
|
||||||
report['table_validation'] = table_guard.validate_tables(run.plan,run.tables)
|
report['table_validation'] = table_guard.validate_tables(run.plan,run.tables)
|
||||||
if previous:
|
if previous:
|
||||||
for key in ('device','sm_arch','table_sha256','build_metadata'):
|
for key in ('device','sm_arch','table_sha256') + (('build_metadata',) if exact_repeat else ()):
|
||||||
if report[key] != previous[key]:
|
if report[key] != previous[key]:
|
||||||
raise AssertionError(f'audited repeat identity differs: {key}')
|
raise AssertionError(f'audited repeat identity differs: {key}')
|
||||||
initial = ref.cpu.State(tg.initial((64,64),0,64,.0192), tg.initial((128,128),1,64,.0192))
|
analytic = ref.cpu.State(tg.initial((64,64),0,64,.0192), tg.initial((128,128),1,64,.0192))
|
||||||
|
initial = analytic
|
||||||
phase = 'warm_reset'
|
phase = 'warm_reset'
|
||||||
initial, report['warmup'] = warm_and_reset(run, initial, check_cap, audit=previous is not None)
|
initial, report['warmup'] = warm_and_reset(run, initial, check_cap, audit=previous is not None)
|
||||||
maps = run.plan.maps
|
maps = run.plan.maps
|
||||||
@@ -275,9 +290,27 @@ def main():
|
|||||||
moment0 = total(initial, maps)
|
moment0 = total(initial, maps)
|
||||||
report['initial'] = dict(mass=mass0, momentum=moment0[1:].tolist(),
|
report['initial'] = dict(mass=mass0, momentum=moment0[1:].tolist(),
|
||||||
minimum_population=min(float(a[:,m].min()) for a,m in zip(arrays,masks)))
|
minimum_population=min(float(a[:,m].min()) for a,m in zip(arrays,masks)))
|
||||||
if previous and differing_fields(report['initial'],previous['initial']):
|
if exact_repeat and differing_fields(report['initial'],previous['initial']):
|
||||||
raise AssertionError('audited repeat initial state diagnostics differ')
|
raise AssertionError('audited repeat initial state diagnostics differ')
|
||||||
direction0 = ref.directional(ref.cpu.State(*(a.astype(np.float64) for a in arrays)),maps)
|
raw_initial = run._download_stored() if args.shifted else initial
|
||||||
|
direction0 = ref.directional(ref.cpu.State(*(a.astype(np.float64) for a in
|
||||||
|
(raw_initial.coarse_f,raw_initial.fine_f))),maps)
|
||||||
|
if args.shifted:
|
||||||
|
errors=[]; unshifted_errors=[]
|
||||||
|
for raw,actual,wanted,mask in zip((raw_initial.coarse_f,raw_initial.fine_f),arrays,
|
||||||
|
(analytic.coarse_f,analytic.fine_f),masks):
|
||||||
|
encoded=gpu._encode_physical(wanted,True)
|
||||||
|
np.testing.assert_array_equal(raw[:,mask].view(np.uint32),encoded[:,mask].view(np.uint32))
|
||||||
|
errors.append(float(abs(actual[:,mask]-wanted[:,mask]).max()))
|
||||||
|
unshifted_errors.append(float(abs(gpu._encode_physical(wanted,False)[:,mask]-wanted[:,mask]).max()))
|
||||||
|
report['warmup']['raw_reset_verified']=True
|
||||||
|
report['initial_quantization']=dict(maximum_population_error_by_level=errors,
|
||||||
|
unshifted_codec_error_by_level=unshifted_errors,
|
||||||
|
moment_change_vs_analytic=(moment0-total(analytic,maps)).tolist(),
|
||||||
|
cpu_start='pinned analytic FP64 trajectory, not reinitialized to decoded GPU upload',
|
||||||
|
matched_cpu_initial_bits=False)
|
||||||
|
report['unshifted_comparison']=dict(status='descriptive_only_codecs_have_different_initial_bits',
|
||||||
|
reference_sha256=AUDIT_SHA,initial=sample_difference(report['initial'],previous['initial']),rows=[])
|
||||||
save()
|
save()
|
||||||
check_cap()
|
check_cap()
|
||||||
phase = 'trajectory'
|
phase = 'trajectory'
|
||||||
@@ -297,6 +330,10 @@ def main():
|
|||||||
row.update(step=step, momentum_change=(total(state,maps)-moment0)[1:].tolist(),
|
row.update(step=step, momentum_change=(total(state,maps)-moment0)[1:].tolist(),
|
||||||
mass_change=row['mass']-mass0,
|
mass_change=row['mass']-mass0,
|
||||||
minimum_population=min(float(a[:,m].min()) for a,m in zip(arrays,masks)))
|
minimum_population=min(float(a[:,m].min()) for a,m in zip(arrays,masks)))
|
||||||
|
if ledger_totals is not None:
|
||||||
|
row['minimum_boundary_pre'] = ledger['minimum_pre']
|
||||||
|
row['minimum_boundary_stored'] = ledger['minimum_stored']
|
||||||
|
else:
|
||||||
with context.activate():
|
with context.activate():
|
||||||
row['minimum_boundary_pre'] = float(run.read(run.pre,(len(run.plan.groups),4),np.float64).min())
|
row['minimum_boundary_pre'] = float(run.read(run.pre,(len(run.plan.groups),4),np.float64).min())
|
||||||
row['minimum_boundary_stored'] = float(run.read(run.h,(len(run.plan.groups),4)).min())
|
row['minimum_boundary_stored'] = float(run.read(run.h,(len(run.plan.groups),4)).min())
|
||||||
@@ -305,10 +342,19 @@ def main():
|
|||||||
raise FloatingPointError('nonfinite sample diagnostic')
|
raise FloatingPointError('nonfinite sample diagnostic')
|
||||||
report['rows'].append(row)
|
report['rows'].append(row)
|
||||||
if previous:
|
if previous:
|
||||||
changed = differing_fields(row,previous['rows'][len(report['rows'])-1])
|
expected=previous['rows'][len(report['rows'])-1]
|
||||||
report['diagnostic']['sample_checks'].append(dict(step=step,differing_fields=changed))
|
changed = differing_fields(row,expected) if exact_repeat else []
|
||||||
endpoint = ref.directional(ref.cpu.State(*(a.astype(np.float64) for a in arrays)),maps)-direction0
|
report['diagnostic']['sample_checks'].append(dict(step=step,
|
||||||
|
status='exact_repeat' if exact_repeat else 'descriptive_only',differing_fields=changed if exact_repeat else None))
|
||||||
|
if args.shifted:
|
||||||
|
report['unshifted_comparison']['rows'].append(dict(step=step,fields=sample_difference(row,expected)))
|
||||||
|
raw_state=run._download_stored() if args.shifted else state
|
||||||
|
endpoint = ref.directional(ref.cpu.State(*(a.astype(np.float64) for a in
|
||||||
|
(raw_state.coarse_f,raw_state.fine_f))),maps)-direction0
|
||||||
prefix = ledger_totals.snapshot(endpoint)
|
prefix = ledger_totals.snapshot(endpoint)
|
||||||
|
if args.shifted:
|
||||||
|
prefix['physical_minus_raw_endpoint_moments'] = (
|
||||||
|
total(state,maps)-moment0-ref.cpu.MOMENTS.T@endpoint).tolist()
|
||||||
report['diagnostic']['prefixes'].append(prefix)
|
report['diagnostic']['prefixes'].append(prefix)
|
||||||
if not prefix['closure_pass'] or not prefix['component_split_pass']:
|
if not prefix['closure_pass'] or not prefix['component_split_pass']:
|
||||||
raise AssertionError('cumulative ledger does not close within summed existing per-step bounds')
|
raise AssertionError('cumulative ledger does not close within summed existing per-step bounds')
|
||||||
@@ -319,6 +365,9 @@ def main():
|
|||||||
report['trajectory_seconds'] = time.monotonic()-trajectory_started
|
report['trajectory_seconds'] = time.monotonic()-trajectory_started
|
||||||
check_cap()
|
check_cap()
|
||||||
report.update(assess(report['rows'], cpu, uniform, run.health_counts))
|
report.update(assess(report['rows'], cpu, uniform, run.health_counts))
|
||||||
|
if args.shifted:
|
||||||
|
report['historical_pressure_gate'].update(status='historical_shifted_FP32_uniform_vs_shifted_FP32_candidate',
|
||||||
|
matched_storage=True,matched_control=False)
|
||||||
report['completed'] = True
|
report['completed'] = True
|
||||||
report['passed'] = report['campaign_pass'] and report['experimental_pass']
|
report['passed'] = report['campaign_pass'] and report['experimental_pass']
|
||||||
if previous:
|
if previous:
|
||||||
|
|||||||
@@ -277,6 +277,16 @@ class Host(unittest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError,'pinned reference'):
|
with self.assertRaisesRegex(ValueError,'pinned reference'):
|
||||||
trajectory.load_references(path,path)
|
trajectory.load_references(path,path)
|
||||||
|
|
||||||
|
def test_shifted_descriptive_comparison_and_runner_pin(self):
|
||||||
|
expected=dict(mass=4096.,momentum=[0.,-1e-4])
|
||||||
|
actual=dict(mass=4096.+2.**-20,momentum=[-1e-6,-2e-4])
|
||||||
|
difference=trajectory.sample_difference(actual,expected)
|
||||||
|
self.assertEqual(difference['mass']['signed'],2.**-20)
|
||||||
|
self.assertEqual(difference['momentum']['absolute'],[1e-6,1e-4])
|
||||||
|
self.assertEqual(trajectory.sha(Path(gpu.__file__).with_suffix('.cu')),trajectory.CUDA_SHA)
|
||||||
|
self.assertEqual(trajectory.LIMITS,dict(velocity_l2=.05,pressure_l2=.15,energy_relative=.05,mass_relative=1e-5))
|
||||||
|
self.assertEqual(trajectory.FACTORS,dict(velocity_l2=.8,pressure_l2=1.,energy_relative=1.))
|
||||||
|
|
||||||
def test_n64_table_guard_and_balanced_mutation(self):
|
def test_n64_table_guard_and_balanced_mutation(self):
|
||||||
guard=trajectory.table_guard
|
guard=trajectory.table_guard
|
||||||
plan=ref.Plan(64,(8,8,56,48)); tables=gpu.pack_plan(plan)
|
plan=ref.Plan(64,(8,8,56,48)); tables=gpu.pack_plan(plan)
|
||||||
|
|||||||
Reference in new issue
Block a user