"""Package selected Gemma adapters, both comparison arms and documented results.""" import hashlib import json import shutil import zipfile from pathlib import Path import requests ROOT=Path(__file__).resolve().parent SCRIPTS=['gemma_classifier.py','train_gemma.py','metrics_gemma.py','prepare_gemma_data.py','prepare_tasks.py', 'audit_synthetic_overlap.py','test_gemma_contract.py','predict_windows.py','serve_models.py','api_access.py', 'api_demo.html','test_api_access.py','API_USAGE.md','WINDOWING_GUIDE.md','GEMMA_CLASSIFIERS_GUIDE.md', 'requirements-gemma-wsl.lock.txt','requirements-windows-cpu.lock.txt','package_gemma.py', 'verify_gemma_inference.py','verify_gemma_publication.py','test_gemma_windows.py'] def results(run,reference): lines=[f'# {run["task"]}: Gemma results','', 'Root adapter selected using validation only, before test evaluation. F1 is not accuracy.', '', '| Arm | Validation micro F1 | Validation macro F1 | Test micro F1 | Test macro F1 | Threshold |', '| --- | ---: | ---: | ---: | ---: | ---: |'] for arm,r in run['arms'].items(): lines.append(f"| {arm} | {r['validation']['micro_f1']:.4f} | {r['validation']['macro_f1']:.4f} | {r['test']['micro_f1']:.4f} | {r['test']['macro_f1']:.4f} | {r['threshold']:.2f} |") lines += ['',f"Selected: **{run['decision']['selected_arm']}**. Synthetic accepted: **{run['decision']['synthetic_accepted']}**.",'', '## RoBERTa reference on the same held-out data','', f"Micro F1: {reference['micro_f1']:.4f}; macro F1: {reference['macro_f1']:.4f}.", 'Different tokenizer/model size/precision/training budgets: practical comparison, not architecture-only ablation. These test results did not select an arm.', '', '| Selected label | Precision | Recall | F1 | Support |','| --- | ---: | ---: | ---: | ---: |'] for label,m in run['arms'][run['decision']['selected_arm']]['test']['per_label'].items(): lines.append(f"| {label} | {m['precision']:.4f} | {m['recall']:.4f} | {m['f1']:.4f} | {m['support']} |") lines+=['',f"Full experiment: {run['elapsed_seconds']/60:.2f} minutes. Peak PyTorch allocated GPU memory: {run['peak_gpu_allocated_bytes']/2**30:.2f} GiB; reserved: {run['peak_gpu_reserved_bytes']/2**30:.2f} GiB. These are not total process/system memory.",'', '| Arm | Epoch | Mean training loss | Validation micro F1 | Validation macro F1 |', '| --- | ---: | ---: | ---: | ---: |'] for arm,r in run['arms'].items(): for h in r['history']: lines.append(f"| {arm} | {h['epoch']} | {h['mean_train_loss']:.5f} | {h['validation']['micro_f1']:.4f} | {h['validation']['macro_f1']:.4f} |") return '\n'.join(lines)+'\n' def main(): license_text=requests.get('https://www.apache.org/licenses/LICENSE-2.0.txt',timeout=30).text assert 'Apache License' in license_text and 'Version 2.0' in license_text for task in ['open-emotions','open-morals','open-sensations']: out=ROOT/'outputs/gemma'/task;run=json.loads((out/'run.json').read_text());assert run['completed'] for name,digest in run['source_sha256'].items():assert hashlib.sha256((ROOT/name).read_bytes()).hexdigest()==digest,name stage=out/'publish' if stage.exists():raise RuntimeError('Existing Gemma staging: '+str(stage)) shutil.copytree(out/run['decision']['selected_arm']/'model',stage) old=json.loads((ROOT/'outputs'/task/'run.json').read_text()) if task=='open-emotions': reference=json.loads((ROOT/'outputs/open-emotions/test_metrics.json').read_text()) else:reference=old['arms'][old['decision']['selected_arm']]['test'] (stage/'roberta_test_reference.json').write_text(json.dumps(reference,indent=2)+'\n',encoding='utf-8') body=results(run,reference) (out/'RESULTS.md').write_text(body,encoding='utf-8') (stage/'RESULTS.md').write_text(body,encoding='utf-8') data=ROOT/'data'/('open-emotions-gemma' if task=='open-emotions' else task) for src,name in [(out/'run.json','run.json'),(out/'experiment_plan.json','experiment_plan.json'), (out/'decision.json','release_decision.json'),(data/'data_manifest.json','data_manifest.json'), (data/'synthetic.jsonl','synthetic_data.jsonl')]:shutil.copy(src,stage/name) (stage/'roberta_reference.json').write_text(json.dumps(old,indent=2)+'\n',encoding='utf-8') ids={s:[json.loads(line)['id'] for line in (data/f'{s}.jsonl').read_text(encoding='utf-8').splitlines()] for s in ['train','validation','test']} (stage/'split_ids.json').write_text(json.dumps(ids,indent=2)+'\n',encoding='utf-8') for name in SCRIPTS:shutil.copy(ROOT/name,stage/name) verification=json.loads((ROOT/'outputs/gemma/inference_verification.json').read_text()) assert verification['completed'] and verification['rate_limit_verified'] (stage/'inference_verification.json').write_text(json.dumps(verification,indent=2)+'\n',encoding='utf-8') (stage/'LICENSE').write_text(license_text,encoding='utf-8') (stage/'NOTICE').write_text('Gemma 4 base model: Google DeepMind, Apache 2.0.\nTask adaptation and classification code: Copyright 2026 mts-the-architect.\nHuman datasets retain their credited source licenses.\n',encoding='utf-8') for arm,filename in [('baseline','human-baseline.zip'),('synthetic','synthetic-candidate.zip'),('real-only','real-only-control.zip')]: with zipfile.ZipFile(stage/filename,'w',compression=zipfile.ZIP_DEFLATED) as archive: for p in (out/arm/'model').iterdir(): if p.name!='README.md':archive.write(p,arcname=p.name) archive.writestr('LICENSE',license_text) archive.writestr('NOTICE',(stage/'NOTICE').read_text(encoding='utf-8')) archive.writestr('README.md',f'# {task}: {arm} adapter\n\nRequires pinned Gemma base and custom loader. Root selection: {run["decision"]["selected_arm"]}.\n\n'+body) repo=f'mts-the-architect/{task}-gemma4-e2b-bf16-lora' card=f'''--- language: en license: apache-2.0 base_model: google/gemma-4-E2B datasets: - {run['data']['dataset']} pipeline_tag: text-classification tags: - gemma4 - peft - lora - multi-label-classification - text-only - research-prototype --- # {task}: Gemma 4 E2B text classifier Separate Gemma-based Open classifier with the same task labels and per-window JSON contract as its RoBERTa counterpart. **Adapter + classifier head download, not standalone base weights.** Run with `gemma_classifier.py`; a generic chat/generation pipeline does not implement this classifier. Selected Gemma held-out test micro/macro F1: **{run['arms'][run['decision']['selected_arm']]['test']['micro_f1']:.4f} / {run['arms'][run['decision']['selected_arm']]['test']['macro_f1']:.4f}**. RoBERTa reference on the same split: **{reference['micro_f1']:.4f} / {reference['macro_f1']:.4f}**. These are classification F1 scores, not accuracy or a general intelligence rating. Different adaptation budgets and model sizes limit architecture-only conclusions. Pinned base: `google/gemma-4-E2B` at `{run['base_revision']}`. The loader reads only text-backbone tensors; no vision/audio encoders, generative head, tools or chat outputs. Actual parameters including adapters/head: {run['parameter_counts']['total']:,}; trainable: {run['parameter_counts']['trainable']:,}. E2B is Google's effective-size designation, not the full parameter count. BF16 backbone; FP32 head and trainable adapters. Default root is the **{run['decision']['selected_arm']}** arm. The human baseline and both continuations are also separate ZIP downloads. Labels in exact order: {', '.join(run['labels'])}. Input format: {run['data']['input_format']}. For sensations, the target word must be identified: `Target: WORD` followed by newline and `Context: SENTENCE`. This remains a small target-word sensory association prototype, not a general sentence-level sensation detector. ## Training, hardware and reproducibility {run['hardware']}. One local GPU; no cloud training. Settings: `{json.dumps(run['settings'])}`. Versions: `{json.dumps(run['versions'])}`. Human and evaluation data preserve the original RoBERTa splits. Full metadata, source hashes and pinned dataset revisions are in `data_manifest.json` and `run.json`; per-epoch histories, synthetic provenance and replay IDs are preserved. Raw human texts are not redistributed. Synthetic data is assistant-authored and not independently human-reviewed. Both continuation arms replay the same sampled human data; synthetic adds examples/compute. See [learning guide](GEMMA_CLASSIFIERS_GUIDE.md). ## Evaluation {body} ## Download and classify Use the pinned model-library environment described in `requirements-gemma-wsl.lock.txt`, with hardware-appropriate PyTorch. Download this repository's adapter/head/tokenizer/config files with `huggingface_hub.snapshot_download('{repo}', local_dir='./downloaded-task')`. The custom loader also obtains the pinned public base (approximately 10.25 GB once in cache). It instantiates only the text backbone; adapters are not autonomous models. ```python import sys sys.path.insert(0, './downloaded-task') # Find the downloaded custom loader. from gemma_classifier import load_classifier import torch model, tokenizer, options = load_classifier('./downloaded-task', device='cuda') model.eval() text = {'"Target: aroma\\nContext: The aroma of bread filled the bakery."' if task=='open-sensations' else '"Thank you for helping me!"' if task=='open-emotions' else '"Everyone deserves the same rights."'} inputs = tokenizer(text, return_tensors='pt', truncation=True, max_length=options['max_length']) with torch.inference_mode(): scores = model(**inputs).logits.sigmoid()[0].cpu().tolist() labels = [model.config.id2label[i] for i, s in enumerate(scores) if s >= options['global_threshold']] ``` For the existing window evidence format, use `python ./downloaded-task/predict_windows.py --task {task} --model ./downloaded-task --file document.txt{' --target aroma' if task=='open-sensations' else ''}`. [Windowing guide](WINDOWING_GUIDE.md) documents overlap and uncalibrated max/mean summaries. [API instructions](API_USAGE.md) describe the same routes, owner approvals and rate limits; pass `--model-dir {task}=./downloaded-task`. Uploading these files does not deploy a public HTTP service. The private API database is never included. ## Limits and attribution Research models: restricted data coverage, label disagreement, rare-category instability and small sensory supports. Gemma size does not guarantee higher classification quality or explicit reasoning. Sigmoid outputs are not calibrated probabilities; short-input thresholds do not establish document-level decisions. No model judges moral truth or measures actual sensations. Training/validation selection is independent of test results. Base weights: [Google Gemma 4](https://huggingface.co/google/gemma-4-E2B), Apache 2.0; [official card](https://ai.google.dev/gemma/docs/core/model_card_4). Human data: [GoEmotions](https://huggingface.co/datasets/google-research-datasets/go_emotions), Apache 2.0; [MFRC](https://huggingface.co/datasets/USC-MOLA-Lab/MFRC), CC BY 4.0; [Contextualized Sensorimotor Norms](https://huggingface.co/datasets/seantrott/cs_norms), CC BY 4.0. Dataset licenses are distinct from model/code licensing. See LICENSE/NOTICE and source references in the learning guide. ''' (stage/'README.md').write_text(card,encoding='utf-8') hashes={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in stage.iterdir() if p.is_file()} (stage/'artifact_hashes.json').write_text(json.dumps(hashes,indent=2)+'\n',encoding='utf-8') print(task,'packaged',len(hashes)+1,'files',flush=True) if __name__=='__main__':main()