approaches/fair-expectimax/rust-engine/experiments/optimization-check/summarize.pyPython87 lines · 6.1 KB
#!/usr/bin/env python3
"""Derive compact descriptive CHECK evidence from all retained repetitions."""
import argparse
import hashlib
import json
import pathlib
import statistics as stats


def spread(values):
    values = [v for v in values if v is not None]
    return {'median': stats.median(values), 'min': min(values), 'max': max(values)} if values else None


def summarize(run):
    result = {'format': 'drop7-rust-optimization-check-v1', 'rootsPerRepeat': 6,
              'repeats': 5, 'strata': 7, 'machine': 'Apple M3 Pro',
              'timingScope': 'Interactive shared host, descriptive CHECK; no gameplay or strength claim.',
              'wallMetricScope': {'compareAndSweep': 'Sum of six search timers, excluding table allocation and process startup; micro/unpack use their inner-loop timer.',
                                  'parallel': 'Sum of six decision wall timers, including planning, allocation, execution and reduction, excluding process startup.'},
              'counterScope': 'Whole measured process including harness, setup and output; macOS printed CPU seconds have 0.01 second resolution.',
              'phases': {}, 'comparisons': [], 'cacheSweep': [], 'parallelSweep': [], 'parallelComparisons': []}
    total = 0
    for phase in ['leaf-compare', 'compare', 'sweep', 'parallel']:
        rows = [json.loads(s) for s in (run / phase / 'measurements.jsonl').read_text().splitlines()]
        expected = 60 if phase == 'parallel' else 50
        assert len(rows) == expected, f'{phase}: partial batch {len(rows)}/{expected}'
        assert all(r['exitCode'] == 0 for r in rows)
        total += sum(r['elapsedSeconds'] for r in rows)
        groups = {}
        for group, arm in dict.fromkeys((r['group'], r['arm']) for r in rows):
            samples = [r for r in rows if (r['group'], r['arm']) == (group, arm)]
            assert sorted(r['repeat'] for r in samples) == list(range(5))
            sample_rows = [r for sample in samples for r in sample['rows'] if r.get('mode') == 'search' or r.get('recordType') == 'decision']
            entry = {'group': group, 'arm': arm, 'repeats': 5}
            for field in ['searchSeconds', 'processCpuSeconds', 'instructions', 'cycles', 'peakRssBytes', 'work']:
                entry[field] = spread([r[field] for r in samples])
            entry['processElapsedSeconds'] = spread([r['elapsedSeconds'] for r in samples])
            entry['initializationSeconds'] = spread([
                sum(r.get('initializationSeconds', r.get('metrics', {}).get('initializationSeconds', 0)) for r in s['rows'])
                for s in samples if any('initializationSeconds' in r or 'initializationSeconds' in r.get('metrics', {}) for r in s['rows'])
            ])
            entry['tableBytes'] = sorted(set(r.get('tableBytes', r.get('metrics', {}).get('projectedTableBytes', 0)) for r in sample_rows))
            entry['leafCalls'] = spread([sum(r['calls'] if r.get('mode') == 'micro' else r.get('leafCalls', r.get('metrics', {}).get('leafCalls', 0)) for r in s['rows']) for s in samples])
            entry['cacheHits'] = spread([sum(r.get('cacheHits', r.get('metrics', {}).get('cacheHits', 0)) for r in s['rows']) for s in samples])
            groups[(group, arm)] = entry
        result['phases'][phase] = list(groups.values())
        if phase in ['compare', 'leaf-compare']:
            for group in dict.fromkeys(r['group'] for r in rows):
                before, after = groups[(group, 'baseline')], groups[(group, 'candidate')]
                b = sorted([r for r in rows if r['group'] == group and r['arm'] == 'baseline'], key=lambda r: r['repeat'])
                a = sorted([r for r in rows if r['group'] == group and r['arm'] == 'candidate'], key=lambda r: r['repeat'])
                entry = {'phase': phase, 'group': group,
                         'wallSpeedRatio': before['searchSeconds']['median'] / after['searchSeconds']['median'],
                         'pairedWallSpeedRatios': spread([x['searchSeconds'] / y['searchSeconds'] for x, y in zip(b, a)]),
                         'instructionReductionPercent': 100 * (1 - after['instructions']['median'] / before['instructions']['median']),
                         'baselineSeconds': before['searchSeconds']['median'], 'candidateSeconds': after['searchSeconds']['median'],
                         'baselineInstructions': before['instructions']['median'], 'candidateInstructions': after['instructions']['median']}
                result['comparisons'].append(entry)
        elif phase == 'sweep':
            result['cacheSweep'] = list(groups.values())
        else:
            result['parallelSweep'] = list(groups.values())
            for group in dict.fromkeys(r['group'] for r in rows):
                private, shared = groups[(group, 'private')], groups[(group, 'shared')]
                p = sorted([r for r in rows if r['group'] == group and r['arm'] == 'private'], key=lambda r: r['repeat'])
                s = sorted([r for r in rows if r['group'] == group and r['arm'] == 'shared'], key=lambda r: r['repeat'])
                result['parallelComparisons'].append({
                    'group': group,
                    'wallSpeedRatio': private['searchSeconds']['median'] / shared['searchSeconds']['median'],
                    'pairedWallSpeedRatios': spread([a['searchSeconds'] / b['searchSeconds'] for a, b in zip(p, s)]),
                    'workReductionPercent': 100 * (1 - shared['work']['median'] / private['work']['median']),
                    'instructionReductionPercent': 100 * (1 - shared['instructions']['median'] / private['instructions']['median']),
                })
    result['timedProcessCount'] = 210
    result['timedProcessSeconds'] = total
    assert total <= 1800, 'protocol timing budget exceeded'
    result['artifactSha256'] = {str(path.relative_to(run)): hashlib.sha256(path.read_bytes()).hexdigest()
                               for path in sorted(run.glob('*/measurements.jsonl'))}
    return result


if __name__ == '__main__':
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument('run', type=pathlib.Path)
    args = p.parse_args()
    (args.run / 'summary.json').write_text(json.dumps(summarize(args.run), indent=2) + '\n')