Spaces:
Sleeping
Sleeping
Upload live inference API deployment files
Browse files- .dockerignore +16 -0
- .gitattributes +1 -0
- Dockerfile +16 -0
- data/model_runs/synthetic_v1_baseline/README.md +87 -0
- data/model_runs/synthetic_v1_baseline/calibration_metrics.json +119 -0
- data/model_runs/synthetic_v1_baseline/classification_report.json +45 -0
- data/model_runs/synthetic_v1_baseline/confusion_matrix.csv +6 -0
- data/model_runs/synthetic_v1_baseline/evidence_audit_sample.csv +51 -0
- data/model_runs/synthetic_v1_baseline/excluded_columns.json +76 -0
- data/model_runs/synthetic_v1_baseline/feature_columns.json +154 -0
- data/model_runs/synthetic_v1_baseline/feature_importance.csv +153 -0
- data/model_runs/synthetic_v1_baseline/manifest.json +127 -0
- data/model_runs/synthetic_v1_baseline/metrics.json +931 -0
- data/model_runs/synthetic_v1_baseline/model.joblib +3 -0
- data/model_runs/synthetic_v1_baseline/predictions_all.csv +3 -0
- data/model_runs/synthetic_v1_baseline/predictions_test.csv +0 -0
- data/model_runs/synthetic_v1_baseline/preprocessing.joblib +3 -0
- data/model_runs/synthetic_v1_baseline/split_manifest.json +0 -0
- data/model_runs/synthetic_v1_baseline/stress_metrics.json +707 -0
- data/model_runs/synthetic_v1_baseline/stress_summary.md +50 -0
- data/model_runs/synthetic_v1_baseline/validation_summary.md +43 -0
- src/datacenter_verification_api/__init__.py +5 -0
- src/datacenter_verification_api/app.py +111 -0
- src/datacenter_verification_api/feature_completion.py +298 -0
- src/datacenter_verification_api/model_service.py +249 -0
- src/datacenter_verification_api/requirements.txt +7 -0
- src/datacenter_verification_api/schemas.py +80 -0
- src/datacenter_verification_api/smoke_test.py +96 -0
- src/datacenter_verification_modeling/README.md +111 -0
- src/datacenter_verification_modeling/__init__.py +6 -0
- src/datacenter_verification_modeling/common.py +611 -0
- src/datacenter_verification_modeling/evaluate_model.py +704 -0
- src/datacenter_verification_modeling/evaluate_stress_splits.py +366 -0
- src/datacenter_verification_modeling/predict.py +30 -0
- src/datacenter_verification_modeling/prepare_features.py +49 -0
- src/datacenter_verification_modeling/requirements.txt +5 -0
- src/datacenter_verification_modeling/rule_baseline.py +192 -0
- src/datacenter_verification_modeling/train_model.py +226 -0
.dockerignore
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.DS_Store
|
| 3 |
+
*__pycache__*
|
| 4 |
+
*.pyc
|
| 5 |
+
xx_private
|
| 6 |
+
|
| 7 |
+
data/synthetic_v0
|
| 8 |
+
data/model_runs/synthetic_v0_baseline
|
| 9 |
+
|
| 10 |
+
data/synthetic_v1/raw_normalized
|
| 11 |
+
data/synthetic_v1/examples
|
| 12 |
+
data/synthetic_v1/schemas
|
| 13 |
+
data/synthetic_v1/validation
|
| 14 |
+
data/synthetic_v1/workbook_rules
|
| 15 |
+
data/synthetic_v1/features/*
|
| 16 |
+
!data/synthetic_v1/features/window_features_all.csv
|
.gitattributes
CHANGED
|
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
data/synthetic_v1/features/window_features_all.csv filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
data/synthetic_v1/features/window_features_all.csv filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/model_runs/synthetic_v1_baseline/predictions_all.csv filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONUNBUFFERED=1
|
| 4 |
+
ENV DCV_MODEL_RUN_DIR=data/model_runs/synthetic_v1_baseline
|
| 5 |
+
ENV DCV_FEATURE_TABLE=data/synthetic_v1/features/window_features_all.csv
|
| 6 |
+
|
| 7 |
+
WORKDIR /app
|
| 8 |
+
|
| 9 |
+
COPY src/datacenter_verification_api/requirements.txt /tmp/requirements-api.txt
|
| 10 |
+
RUN pip install --no-cache-dir -r /tmp/requirements-api.txt
|
| 11 |
+
|
| 12 |
+
COPY src ./src
|
| 13 |
+
COPY data/model_runs/synthetic_v1_baseline ./data/model_runs/synthetic_v1_baseline
|
| 14 |
+
COPY data/synthetic_v1/features/window_features_all.csv ./data/synthetic_v1/features/window_features_all.csv
|
| 15 |
+
|
| 16 |
+
CMD ["sh", "-c", "uvicorn src.datacenter_verification_api.app:app --host 0.0.0.0 --port ${PORT:-7860}"]
|
data/model_runs/synthetic_v1_baseline/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# synthetic_v1_baseline model run
|
| 2 |
+
|
| 3 |
+
This directory contains a public runnable baseline for the `synthetic_v1` datacenter training-run verification dataset.
|
| 4 |
+
|
| 5 |
+
## Dataset
|
| 6 |
+
|
| 7 |
+
- Feature table: `data/synthetic_v1/features/window_features_all.csv`
|
| 8 |
+
- Rows: 14712
|
| 9 |
+
- Episode split: grouped by `episode_id`, scenario-stratified, seed `20260510`
|
| 10 |
+
- Split rows: train 8870, validation 2937, test 2905
|
| 11 |
+
- Split episodes: train 458, validation 152, test 150
|
| 12 |
+
|
| 13 |
+
## Model
|
| 14 |
+
|
| 15 |
+
- Supervised model: calibrated scikit-learn histogram gradient boosting classifier
|
| 16 |
+
- Calibration: validation split only, held-out test evaluated once
|
| 17 |
+
- Rule baseline: deterministic evidence rules in `src/datacenter_verification_modeling/rule_baseline.py`
|
| 18 |
+
- Leakage exclusions: identifiers, labels, site id, episode id, raw manifest hash, scenario metadata, counterfactual metadata, and synthetic-only audit columns
|
| 19 |
+
|
| 20 |
+
## Headline Test Metrics
|
| 21 |
+
|
| 22 |
+
- Accuracy: 0.9725
|
| 23 |
+
- Macro F1: 0.9781
|
| 24 |
+
- Weighted F1: 0.9723
|
| 25 |
+
- Log loss: 0.1057
|
| 26 |
+
- Label 3/4 precision by predicted label: 0.9935
|
| 27 |
+
- Label 3/4 recall by predicted label: 0.9967
|
| 28 |
+
- `p_large_training >= 0.5` precision: 0.9935
|
| 29 |
+
- `p_large_training >= 0.5` recall: 0.9967
|
| 30 |
+
- Rule baseline macro F1: 0.3636
|
| 31 |
+
|
| 32 |
+
## Error Scenarios
|
| 33 |
+
|
| 34 |
+
- Largest false-positive scenarios at `p_large_training >= 0.5`: underclocked_energy_capped_training: 4
|
| 35 |
+
- Largest false-negative scenarios at `p_large_training >= 0.5`: fragmented_training_linked: 2
|
| 36 |
+
|
| 37 |
+
## Calibration
|
| 38 |
+
|
| 39 |
+
- Brier score for `p_large_training`: 0.0018
|
| 40 |
+
- Expected calibration error for `p_large_training`: 0.0105
|
| 41 |
+
|
| 42 |
+
## Stress Splits
|
| 43 |
+
|
| 44 |
+
Stress outputs are in:
|
| 45 |
+
|
| 46 |
+
- `stress_metrics.json`
|
| 47 |
+
- `stress_summary.md`
|
| 48 |
+
|
| 49 |
+
Key stress checks from the lightweight retraining script:
|
| 50 |
+
|
| 51 |
+
- Episode-grouped random split macro F1: 0.9820; large-training recall: 0.9967
|
| 52 |
+
- Time holdout macro F1: 0.9863; large-training recall: 1.0000
|
| 53 |
+
- Underclocked holdout large-training recall: 1.0000; false negatives: 0
|
| 54 |
+
- Fragmented linked training holdout remains hard: 91 large-training false negatives
|
| 55 |
+
- Source ablation dropping runtime and ML logs changed macro F1 by -0.089 versus the random stress baseline
|
| 56 |
+
- Middle `p_large_training` bins are populated in stress checks: {'0.1_to_0.3': 18, '0.3_to_0.7': 2, '0.7_to_0.9': 0}
|
| 57 |
+
|
| 58 |
+
## Reproduce
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
python src/datacenter_verification_modeling/train_model.py \
|
| 62 |
+
--features data/synthetic_v1/features/window_features_all.csv \
|
| 63 |
+
--output data/model_runs/synthetic_v1_baseline \
|
| 64 |
+
--seed 20260510
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
python src/datacenter_verification_modeling/evaluate_model.py \
|
| 69 |
+
--model-run data/model_runs/synthetic_v1_baseline \
|
| 70 |
+
--features data/synthetic_v1/features/window_features_all.csv
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
```bash
|
| 74 |
+
python src/datacenter_verification_modeling/predict.py \
|
| 75 |
+
--model-run data/model_runs/synthetic_v1_baseline \
|
| 76 |
+
--features data/synthetic_v1/features/window_features_all.csv \
|
| 77 |
+
--output data/model_runs/synthetic_v1_baseline/predictions_all.csv
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
## Limitations
|
| 81 |
+
|
| 82 |
+
- This model is trained on synthetic data only.
|
| 83 |
+
- Performance numbers are not real-world deployment claims.
|
| 84 |
+
- Adjacent windows are correlated, so group splitting by `episode_id` is mandatory.
|
| 85 |
+
- Synthetic labels are generated from rules and latent scenarios, so the model may learn generator assumptions.
|
| 86 |
+
- Real datacenter deployment would require calibration on real telemetry and controlled drills.
|
| 87 |
+
- The model should assist audit triage; it should not be treated as sole proof of a violation.
|
data/model_runs/synthetic_v1_baseline/calibration_metrics.json
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"brier_large_training": 0.001846207706971921,
|
| 3 |
+
"calibration_by_label": {
|
| 4 |
+
"0": {
|
| 5 |
+
"brier": 0.00841142332402854,
|
| 6 |
+
"mean_predicted_probability": 0.2744490972864224,
|
| 7 |
+
"observed_fraction": 0.2623063683304647,
|
| 8 |
+
"rows": 2905
|
| 9 |
+
},
|
| 10 |
+
"1": {
|
| 11 |
+
"brier": 0.024583974737105475,
|
| 12 |
+
"mean_predicted_probability": 0.21573853144684202,
|
| 13 |
+
"observed_fraction": 0.23752151462994836,
|
| 14 |
+
"rows": 2905
|
| 15 |
+
},
|
| 16 |
+
"2": {
|
| 17 |
+
"brier": 0.018264729634158587,
|
| 18 |
+
"mean_predicted_probability": 0.3017207163585596,
|
| 19 |
+
"observed_fraction": 0.2884681583476764,
|
| 20 |
+
"rows": 2905
|
| 21 |
+
},
|
| 22 |
+
"3": {
|
| 23 |
+
"brier": 0.0017921366976442609,
|
| 24 |
+
"mean_predicted_probability": 0.1570568215171177,
|
| 25 |
+
"observed_fraction": 0.15938037865748708,
|
| 26 |
+
"rows": 2905
|
| 27 |
+
},
|
| 28 |
+
"4": {
|
| 29 |
+
"brier": 7.20109313866924e-05,
|
| 30 |
+
"mean_predicted_probability": 0.05103483339105826,
|
| 31 |
+
"observed_fraction": 0.05232358003442341,
|
| 32 |
+
"rows": 2905
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"expected_calibration_error_large_training": 0.010488907264903001,
|
| 36 |
+
"log_loss": 0.10567154528993643,
|
| 37 |
+
"reliability_bins_large_training": [
|
| 38 |
+
{
|
| 39 |
+
"bin_index": 0,
|
| 40 |
+
"count": 2283,
|
| 41 |
+
"lower": 0.0,
|
| 42 |
+
"mean_predicted_probability": 0.002786695213286837,
|
| 43 |
+
"observed_fraction": 0.0,
|
| 44 |
+
"upper": 0.1
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"bin_index": 1,
|
| 48 |
+
"count": 0,
|
| 49 |
+
"lower": 0.1,
|
| 50 |
+
"mean_predicted_probability": 0.0,
|
| 51 |
+
"observed_fraction": 0.0,
|
| 52 |
+
"upper": 0.2
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"bin_index": 2,
|
| 56 |
+
"count": 2,
|
| 57 |
+
"lower": 0.2,
|
| 58 |
+
"mean_predicted_probability": 0.24783574032227917,
|
| 59 |
+
"observed_fraction": 1.0,
|
| 60 |
+
"upper": 0.30000000000000004
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"bin_index": 3,
|
| 64 |
+
"count": 3,
|
| 65 |
+
"lower": 0.30000000000000004,
|
| 66 |
+
"mean_predicted_probability": 0.30455095537998655,
|
| 67 |
+
"observed_fraction": 0.0,
|
| 68 |
+
"upper": 0.4
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"bin_index": 4,
|
| 72 |
+
"count": 0,
|
| 73 |
+
"lower": 0.4,
|
| 74 |
+
"mean_predicted_probability": 0.0,
|
| 75 |
+
"observed_fraction": 0.0,
|
| 76 |
+
"upper": 0.5
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"bin_index": 5,
|
| 80 |
+
"count": 8,
|
| 81 |
+
"lower": 0.5,
|
| 82 |
+
"mean_predicted_probability": 0.5612793252894144,
|
| 83 |
+
"observed_fraction": 1.0,
|
| 84 |
+
"upper": 0.6000000000000001
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"bin_index": 6,
|
| 88 |
+
"count": 4,
|
| 89 |
+
"lower": 0.6000000000000001,
|
| 90 |
+
"mean_predicted_probability": 0.6781471295808353,
|
| 91 |
+
"observed_fraction": 0.0,
|
| 92 |
+
"upper": 0.7000000000000001
|
| 93 |
+
},
|
| 94 |
+
{
|
| 95 |
+
"bin_index": 7,
|
| 96 |
+
"count": 0,
|
| 97 |
+
"lower": 0.7000000000000001,
|
| 98 |
+
"mean_predicted_probability": 0.0,
|
| 99 |
+
"observed_fraction": 0.0,
|
| 100 |
+
"upper": 0.8
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"bin_index": 8,
|
| 104 |
+
"count": 4,
|
| 105 |
+
"lower": 0.8,
|
| 106 |
+
"mean_predicted_probability": 0.8676498355263033,
|
| 107 |
+
"observed_fraction": 1.0,
|
| 108 |
+
"upper": 0.9
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"bin_index": 9,
|
| 112 |
+
"count": 601,
|
| 113 |
+
"lower": 0.9,
|
| 114 |
+
"mean_predicted_probability": 0.9751439027068034,
|
| 115 |
+
"observed_fraction": 1.0,
|
| 116 |
+
"upper": 1.0
|
| 117 |
+
}
|
| 118 |
+
]
|
| 119 |
+
}
|
data/model_runs/synthetic_v1_baseline/classification_report.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"0": {
|
| 3 |
+
"f1-score": 0.9915419648666233,
|
| 4 |
+
"precision": 0.983225806451613,
|
| 5 |
+
"recall": 1.0,
|
| 6 |
+
"support": 762.0
|
| 7 |
+
},
|
| 8 |
+
"1": {
|
| 9 |
+
"f1-score": 0.9450222882615156,
|
| 10 |
+
"precision": 0.9695121951219512,
|
| 11 |
+
"recall": 0.9217391304347826,
|
| 12 |
+
"support": 690.0
|
| 13 |
+
},
|
| 14 |
+
"2": {
|
| 15 |
+
"f1-score": 0.96047197640118,
|
| 16 |
+
"precision": 0.9498249708284714,
|
| 17 |
+
"recall": 0.9713603818615751,
|
| 18 |
+
"support": 838.0
|
| 19 |
+
},
|
| 20 |
+
"3": {
|
| 21 |
+
"f1-score": 0.9935344827586207,
|
| 22 |
+
"precision": 0.9913978494623656,
|
| 23 |
+
"recall": 0.9956803455723542,
|
| 24 |
+
"support": 463.0
|
| 25 |
+
},
|
| 26 |
+
"4": {
|
| 27 |
+
"f1-score": 1.0,
|
| 28 |
+
"precision": 1.0,
|
| 29 |
+
"recall": 1.0,
|
| 30 |
+
"support": 152.0
|
| 31 |
+
},
|
| 32 |
+
"accuracy": 0.9724612736660929,
|
| 33 |
+
"macro avg": {
|
| 34 |
+
"f1-score": 0.9781141424575879,
|
| 35 |
+
"precision": 0.9787921643728803,
|
| 36 |
+
"recall": 0.9777559715737423,
|
| 37 |
+
"support": 2905.0
|
| 38 |
+
},
|
| 39 |
+
"weighted avg": {
|
| 40 |
+
"f1-score": 0.9722899614011162,
|
| 41 |
+
"precision": 0.9725136003461651,
|
| 42 |
+
"recall": 0.9724612736660929,
|
| 43 |
+
"support": 2905.0
|
| 44 |
+
}
|
| 45 |
+
}
|
data/model_runs/synthetic_v1_baseline/confusion_matrix.csv
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
true_label,pred_0,pred_1,pred_2,pred_3,pred_4
|
| 2 |
+
true_0,762,0,0,0,0
|
| 3 |
+
true_1,13,636,41,0,0
|
| 4 |
+
true_2,0,20,814,4,0
|
| 5 |
+
true_3,0,0,2,461,0
|
| 6 |
+
true_4,0,0,0,0,152
|
data/model_runs/synthetic_v1_baseline/evidence_audit_sample.csv
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
audit_category,feature_row_id,split,latent_workload_class,true_label,predicted_label,p_large_training,severity_score,top_evidence,critical_missing_layers,integrity_warning,policy_compute_ratio,o2_max_concurrent_normalized_gpus,o2_allocation_duration_hours,o2_gpu_hours_policy_ratio,o4_gpu_util_p95,o4_gpu_util_duty_gt_70,o7_synchronized_fabric_footprint,o7_collective_periodicity_score,o8_rack_power_fraction_p95,o10_runtime_framework_class,o11_checkpoint_periodicity_score,o12_signed_ml_logs_present,o12_declared_parameter_count_b,o14_min_critical_coverage,o14_gap_fraction_critical,o13_confidential_compute_mode_fraction,o2_elastic_resize_count,o2_preemption_restart_count,o2_account_linkage_confidence,o4_hbm_pressure_duration_fraction,o4_power_cap_active_fraction,o7_account_flow_linkage_confidence,o10_runtime_metadata_confidence,o11_artifact_write_pattern_score,o11_dataloader_read_pattern_score,o12_log_delivery_delay_hours,o12_log_completeness_fraction,o4_missing_reason,o7_missing_reason,o12_missing_reason
|
| 2 |
+
correct_label_0,feat_cc640fafc2b8e3abf770c579,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 3 |
+
correct_label_0,feat_b9e262daf17134352518a78d,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 4 |
+
correct_label_0,feat_f2d694eed3d39809a028e2b2,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 5 |
+
correct_label_0,feat_729feb643bec26839d5503c5,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 6 |
+
correct_label_0,feat_3d5fc07aac19d9a5f2f3b3c4,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 7 |
+
correct_label_0,feat_c7f33b2997f139238815270f,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1115,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 8 |
+
correct_label_0,feat_313de45b023c747cb085c198,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1115,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 9 |
+
correct_label_0,feat_ae25d41c049ce4e3cac1242a,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 10 |
+
correct_label_0,feat_a4957cf7cc0d15a01c6fb2e3,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1053,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 11 |
+
correct_label_0,feat_5f0c6f536a6333175af7d1ad,test,idle_or_low_activity,0,0,0.0015660703107509954,0.030981720299867704,no strong positive evidence,,False,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.1549,none,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.0,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 12 |
+
correct_label_2_hard_false_positive,feat_a13e07bfed9432972ef31ffd,test,large_batch_inference,2,2,0.011030947812113877,1.977255258500455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 13 |
+
correct_label_2_hard_false_positive,feat_00e8cf11a42724292298701b,test,large_batch_inference,2,2,0.010478314009615335,1.975675486490715,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.0809,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 14 |
+
correct_label_2_hard_false_positive,feat_cd03bedba3159582ed1baa14,test,embedding_generation,2,2,0.010431098895779199,1.9626186151571463,large allocation; high GPU activity,,False,0.3629,713.64,6.25,0.3629,63.8316,0.3986,164.0,0.1905,0.3809,embedding_generation,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3328,0.0,1.0,1.0,0.0,0.3263,0.0,0.0,observed,observed,privacy_redacted
|
| 15 |
+
correct_label_2_hard_false_positive,feat_34bca06c5eb77d0686eb0b00,test,large_batch_inference,2,2,0.010426946064660568,1.975794732799792,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.094,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 16 |
+
correct_label_2_hard_false_positive,feat_a9949f2525b28aadd5101e47,test,large_batch_inference,2,2,0.010426946064660568,1.975794732799792,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,43.0872,0.2804,124.0,0.094,0.406,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 17 |
+
correct_label_2_hard_false_positive,feat_33b02f78b085c861a75ce235,test,large_batch_inference,2,2,0.0103685521855723,1.9784666216612823,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,82.86,0.5393,226.0,0.1808,0.5639,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 18 |
+
correct_label_2_hard_false_positive,feat_db7205bdba2eac91416428d7,test,large_batch_inference,2,2,0.0103685521855723,1.9784666216612823,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,82.86,0.5393,226.0,0.1808,0.5639,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 19 |
+
correct_label_2_hard_false_positive,feat_9aa413faa9c554abf6d2bb64,test,large_batch_inference,2,2,0.010222344032340501,1.9762696989913593,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1057,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 20 |
+
correct_label_2_hard_false_positive,feat_d7444c225b8334e750e5a74e,test,large_batch_inference,2,2,0.010177838904019004,1.9763730138561455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 21 |
+
correct_label_2_hard_false_positive,feat_123d069e43df582c24b12e43,test,large_batch_inference,2,2,0.010177838904019004,1.9763730138561455,large allocation; long allocation duration; high GPU activity,,False,8.1915,1089.61,92.38,8.1915,56.3448,0.3667,162.0,0.1229,0.3835,large_batch_inference,0.0,False,,0.98,0.01,0.0,0,0,1.0,0.3024,0.0,1.0,1.0,0.0,0.0,0.0,0.0,observed,observed,privacy_redacted
|
| 22 |
+
correct_label_3_4_likely_training,feat_aa9a945cbc067309f88926db,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 23 |
+
correct_label_3_4_likely_training,feat_45062add05716b1857d2d421,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 24 |
+
correct_label_3_4_likely_training,feat_daa683c0ae4f6ebda93534f5,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 25 |
+
correct_label_3_4_likely_training,feat_1a65e998609bc78ecf0dc1cb,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 26 |
+
correct_label_3_4_likely_training,feat_e60eeafb2f9e0d9386d1c5d0,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 27 |
+
correct_label_3_4_likely_training,feat_3fa8d05e390014d891252788,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 28 |
+
correct_label_3_4_likely_training,feat_1f1405339cd56e6df182c550,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 29 |
+
correct_label_3_4_likely_training,feat_2bf4e8f647710e486ecd057c,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 30 |
+
correct_label_3_4_likely_training,feat_af8e945fc6fc7f9343da8288,test,cloud_training_redacted_runtime,3,3,0.9801384593906176,2.9538324770207036,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; checkpoint cadence; signed ML logs,,False,2.4407,664.66,45.12,2.4407,82.83,0.6073,598.0,0.8189,0.4996,pytorch_distributed_training,0.5803,True,516.61,0.98,0.01,0.0,0,0,1.0,0.4217,0.0,1.0,0.25,0.5495,0.567,0.0,0.4157,observed,observed,privacy_redacted
|
| 31 |
+
correct_label_3_4_likely_training,feat_2267762b2b19047a21c23936,test,training_without_semantic_logs,3,3,0.9800541368127997,2.9537374894493977,large allocation; long allocation duration; high GPU activity; training runtime metadata; checkpoint cadence,,False,2.5163,461.48,67.0,2.5163,71.44,0.8187,302.0,0.5238,0.38,pytorch_distributed_training,0.7481,False,,0.98,0.01,0.0,0,0,0.8407,0.4875,0.0,0.8407,0.55,0.6235,0.5428,0.0,0.0,observed,observed,privacy_redacted
|
| 32 |
+
false_positive_large_training,feat_49c6f202eb6d2980105018df,test,underclocked_energy_capped_training,2,3,0.6981856840003529,2.6442344946513,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.2781,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
|
| 33 |
+
false_positive_large_training,feat_a383ed6dabdf9145d8ee996e,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.3695,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
|
| 34 |
+
false_positive_large_training,feat_ee68609d2f2683b7bef8841b,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
|
| 35 |
+
false_positive_large_training,feat_50f4faf6dbd38b6daae6b62f,test,underclocked_energy_capped_training,2,3,0.6714676114409962,2.6140027926406604,large allocation; long allocation duration; high GPU activity; training runtime metadata; signed ML logs; low critical coverage; counter disabled by CC mode,O4:counter_disabled_by_cc_mode:coverage=0.29,True,9.2912,513.05,222.53,9.2912,53.4752,0.5516,267.0,0.4296,0.4283,pytorch_distributed_training,0.1981,True,316.04,0.2921,0.01,0.8046,0,0,1.0,0.3765,0.4654,1.0,0.7611,0.2456,0.7229,0.0,0.342,counter_disabled_by_cc_mode,observed,delayed_log_delivery
|
| 36 |
+
false_negative_large_training,feat_aa8009dde8ce7ceadcee314b,test,fragmented_training_linked,3,2,0.2474303218229664,2.2261162832074306,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,1.1744,260.88,55.32,1.1744,73.74,0.5307,211.0,0.5568,0.38,pytorch_distributed_training,0.4556,False,,0.98,0.01,0.0,0,0,0.2797,0.3251,0.0,0.2797,0.9268,0.3735,0.3108,0.0,0.0,observed,observed,privacy_redacted
|
| 37 |
+
false_negative_large_training,feat_1d405c4b2a8bf7a3fa052e89,test,fragmented_training_linked,3,2,0.24824115882159198,2.226857273426679,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata,,False,1.1744,260.88,55.32,1.1744,73.74,0.5307,211.0,0.6474,0.38,pytorch_distributed_training,0.4556,False,,0.98,0.01,0.0,0,0,0.2797,0.3251,0.0,0.2797,0.9268,0.3735,0.3108,0.0,0.0,observed,observed,privacy_redacted
|
| 38 |
+
high_uncertainty_or_coverage_edge,feat_3652e586595bc4b0f0a73268,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.2928,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
|
| 39 |
+
high_uncertainty_or_coverage_edge,feat_9aff07df6b963210981b227a,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
|
| 40 |
+
high_uncertainty_or_coverage_edge,feat_4877f8beca7baf999dfc875b,test,training_without_semantic_logs,3,3,0.5362924911368747,2.402141567675965,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.2225,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
|
| 41 |
+
high_uncertainty_or_coverage_edge,feat_48db5d097443aaf9625944d9,test,training_with_delayed_logs,3,3,0.5742738161131281,2.555844926309712,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
|
| 42 |
+
high_uncertainty_or_coverage_edge,feat_9089822165b23e10b9ea605c,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.5207,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
|
| 43 |
+
high_uncertainty_or_coverage_edge,feat_d218ca927f7ec4d4efcd5cd2,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
|
| 44 |
+
high_uncertainty_or_coverage_edge,feat_38e806129aa6f1882a2e6ec4,test,training_with_delayed_logs,3,3,0.575021844861732,2.5565689502386792,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,26.8381,1974.23,167.05,26.8381,64.9128,0.5727,1278.0,0.6055,0.3937,pytorch_distributed_training,0.2932,True,254.18,0.98,0.01,0.0,0,0,1.0,0.3817,0.0,1.0,0.8011,0.2015,0.6239,47.3,0.4119,observed,observed,delayed_log_delivery
|
| 45 |
+
high_uncertainty_or_coverage_edge,feat_f8462a0c8142dee9ac8578b1,test,training_without_semantic_logs,3,3,0.5820177782063665,2.458458270477721,large allocation; long allocation duration; high GPU activity; power corroboration; training runtime metadata,,False,2.8294,748.77,46.43,2.8294,59.602,0.4835,310.0,0.3405,0.6028,pytorch_distributed_training,0.3025,False,,0.98,0.01,0.0,0,0,1.0,0.533,0.0,1.0,0.7154,0.2446,0.5083,0.0,0.0,observed,observed,privacy_redacted
|
| 46 |
+
high_uncertainty_or_coverage_edge,feat_414b3f8239f8d77cc1b8fe90,test,fragmented_training_linked,2,2,0.3074817930715641,2.284158029977732,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
|
| 47 |
+
high_uncertainty_or_coverage_edge,feat_ba27f8df9db4f33ee67bbb36,test,fragmented_training_linked,2,2,0.30308553653419773,2.280095247643677,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
|
| 48 |
+
high_uncertainty_or_coverage_edge,feat_6de8a0c0b3e26c7a626710a7,test,fragmented_training_linked,2,2,0.30308553653419773,2.280095247643677,large allocation; long allocation duration; high GPU activity; training runtime metadata,,False,2.8713,272.73,129.37,2.8713,73.56,0.8511,179.0,0.5256,0.38,pytorch_distributed_training,0.4202,False,,0.98,0.01,0.0,0,0,0.2112,0.3986,0.0,0.2112,0.8524,0.5538,0.3507,0.0,0.0,observed,observed,privacy_redacted
|
| 49 |
+
high_uncertainty_or_coverage_edge,feat_b40bf45e96cf6cc4b679357e,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.5216,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
|
| 50 |
+
high_uncertainty_or_coverage_edge,feat_59636d473b9ba2c6c75a8909,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.6066,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
|
| 51 |
+
high_uncertainty_or_coverage_edge,feat_1fc099dcc832d0323269b33f,test,training_with_delayed_logs,4,4,0.8674512750198456,3.7090949558285335,large allocation; long allocation duration; high GPU activity; synchronized scale-out fabric; training runtime metadata; signed ML logs,,False,14.8646,944.34,193.42,14.8646,56.7324,0.4636,611.0,0.6066,0.306,pytorch_distributed_training,0.2137,True,167.81,0.98,0.01,0.0,0,0,1.0,0.3747,0.0,1.0,0.7358,0.2546,0.4936,58.2,0.9151,observed,observed,delayed_log_delivery
|
data/model_runs/synthetic_v1_baseline/excluded_columns.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"missing_requested_excluded_columns": [],
|
| 3 |
+
"notes": [
|
| 4 |
+
"Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
|
| 5 |
+
"scope_type and window_length_seconds are retained because they are valid deployment-time context.",
|
| 6 |
+
"Constant version metadata columns are excluded from the baseline."
|
| 7 |
+
],
|
| 8 |
+
"present_excluded_columns": [
|
| 9 |
+
"feature_row_id",
|
| 10 |
+
"dataset_id",
|
| 11 |
+
"seed",
|
| 12 |
+
"site_id",
|
| 13 |
+
"scope_id_hash",
|
| 14 |
+
"window_start",
|
| 15 |
+
"window_end",
|
| 16 |
+
"episode_id",
|
| 17 |
+
"label_0_to_4",
|
| 18 |
+
"label_confidence",
|
| 19 |
+
"label_reason",
|
| 20 |
+
"label_source",
|
| 21 |
+
"raw_input_manifest_hash",
|
| 22 |
+
"latent_workload_class",
|
| 23 |
+
"scenario_family",
|
| 24 |
+
"scenario_variant",
|
| 25 |
+
"evidence_recipe_id",
|
| 26 |
+
"counterfactual_group_id",
|
| 27 |
+
"synthetic_counterfactual_role",
|
| 28 |
+
"data_quality_regime",
|
| 29 |
+
"privacy_tier",
|
| 30 |
+
"collector_profile",
|
| 31 |
+
"topology_class",
|
| 32 |
+
"temporal_phase",
|
| 33 |
+
"synthetic_hard_case_tags",
|
| 34 |
+
"synthetic_evidence_profile",
|
| 35 |
+
"capacity_evidence_only",
|
| 36 |
+
"integrity_evidence_only",
|
| 37 |
+
"physical_evidence_only",
|
| 38 |
+
"feature_pipeline_version",
|
| 39 |
+
"policy_threshold_version",
|
| 40 |
+
"hardware_normalization_version"
|
| 41 |
+
],
|
| 42 |
+
"requested_excluded_columns": [
|
| 43 |
+
"feature_row_id",
|
| 44 |
+
"dataset_id",
|
| 45 |
+
"seed",
|
| 46 |
+
"site_id",
|
| 47 |
+
"scope_id_hash",
|
| 48 |
+
"window_start",
|
| 49 |
+
"window_end",
|
| 50 |
+
"episode_id",
|
| 51 |
+
"label_0_to_4",
|
| 52 |
+
"label_confidence",
|
| 53 |
+
"label_reason",
|
| 54 |
+
"label_source",
|
| 55 |
+
"raw_input_manifest_hash",
|
| 56 |
+
"latent_workload_class",
|
| 57 |
+
"scenario_family",
|
| 58 |
+
"scenario_variant",
|
| 59 |
+
"evidence_recipe_id",
|
| 60 |
+
"counterfactual_group_id",
|
| 61 |
+
"synthetic_counterfactual_role",
|
| 62 |
+
"data_quality_regime",
|
| 63 |
+
"privacy_tier",
|
| 64 |
+
"collector_profile",
|
| 65 |
+
"topology_class",
|
| 66 |
+
"temporal_phase",
|
| 67 |
+
"synthetic_hard_case_tags",
|
| 68 |
+
"synthetic_evidence_profile",
|
| 69 |
+
"capacity_evidence_only",
|
| 70 |
+
"integrity_evidence_only",
|
| 71 |
+
"physical_evidence_only",
|
| 72 |
+
"feature_pipeline_version",
|
| 73 |
+
"policy_threshold_version",
|
| 74 |
+
"hardware_normalization_version"
|
| 75 |
+
]
|
| 76 |
+
}
|
data/model_runs/synthetic_v1_baseline/feature_columns.json
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
"scope_type",
|
| 3 |
+
"window_length_seconds",
|
| 4 |
+
"capacity_possible",
|
| 5 |
+
"policy_compute_ratio",
|
| 6 |
+
"o1_normalized_h100e_capacity",
|
| 7 |
+
"o1_largest_contiguous_domain_gpus",
|
| 8 |
+
"o1_homogeneous_high_end_fraction",
|
| 9 |
+
"o1_non_partitioned_fraction",
|
| 10 |
+
"o1_inventory_delta_rate",
|
| 11 |
+
"o17_external_capacity_conflict_score",
|
| 12 |
+
"o2_max_concurrent_normalized_gpus",
|
| 13 |
+
"o2_allocation_duration_hours",
|
| 14 |
+
"o2_gpu_hours_policy_ratio",
|
| 15 |
+
"o2_concurrency_fraction_domain",
|
| 16 |
+
"o2_topology_contiguity_score",
|
| 17 |
+
"o2_declared_workload_class",
|
| 18 |
+
"o2_reservation_exclusive_flag",
|
| 19 |
+
"o2_elastic_resize_count",
|
| 20 |
+
"o2_preemption_restart_count",
|
| 21 |
+
"o2_scheduler_queue_delay_hours",
|
| 22 |
+
"o2_account_linkage_confidence",
|
| 23 |
+
"o2_job_array_width",
|
| 24 |
+
"o2_reservation_reuse_count",
|
| 25 |
+
"o3_batch_provisioned_gpus",
|
| 26 |
+
"o3_capacity_reservation_duration_hours",
|
| 27 |
+
"o3_training_sku_fraction",
|
| 28 |
+
"o3_billing_continuity_score",
|
| 29 |
+
"o3_egress_tb",
|
| 30 |
+
"o4_gpu_util_p50",
|
| 31 |
+
"o4_gpu_util_p95",
|
| 32 |
+
"o4_gpu_util_duty_gt_70",
|
| 33 |
+
"o4_sm_tensor_active_p95",
|
| 34 |
+
"o4_hbm_used_fraction_p50",
|
| 35 |
+
"o4_hbm_bandwidth_active_p95",
|
| 36 |
+
"o4_gpu_power_fraction_p95",
|
| 37 |
+
"o4_error_spike_score",
|
| 38 |
+
"o4_gpu_util_cv",
|
| 39 |
+
"o4_gpu_idle_gap_p95_minutes",
|
| 40 |
+
"o4_hbm_pressure_duration_fraction",
|
| 41 |
+
"o4_power_cap_active_fraction",
|
| 42 |
+
"o4_thermal_throttle_fraction",
|
| 43 |
+
"o5_kernel_training_motif_score",
|
| 44 |
+
"o5_tensor_throughput_ratio",
|
| 45 |
+
"o5_profiler_available",
|
| 46 |
+
"o6_nvlink_util_p95",
|
| 47 |
+
"o6_nvlink_periodicity_score",
|
| 48 |
+
"o6_link_error_spike_score",
|
| 49 |
+
"o7_scaleout_port_util_p95",
|
| 50 |
+
"o7_synchronized_fabric_footprint",
|
| 51 |
+
"o7_collective_periodicity_score",
|
| 52 |
+
"o7_burst_duty_cycle",
|
| 53 |
+
"o7_rdma_congestion_score",
|
| 54 |
+
"o7_job_to_port_mapping_coverage",
|
| 55 |
+
"o7_flow_entropy_score",
|
| 56 |
+
"o7_cross_section_sync_score",
|
| 57 |
+
"o7_collective_jitter_score",
|
| 58 |
+
"o7_storage_traffic_fraction",
|
| 59 |
+
"o7_inference_fanout_score",
|
| 60 |
+
"o7_account_flow_linkage_confidence",
|
| 61 |
+
"o8_rack_power_fraction_p95",
|
| 62 |
+
"o8_facility_it_power_mw",
|
| 63 |
+
"o8_baseline_subtracted_energy_kwh",
|
| 64 |
+
"o8_power_continuity_days",
|
| 65 |
+
"o8_power_cv",
|
| 66 |
+
"o8_power_to_gpu_residual",
|
| 67 |
+
"o8_power_baseline_drift_score",
|
| 68 |
+
"o8_power_cap_or_curtailment_active",
|
| 69 |
+
"o8_unattributed_power_fraction",
|
| 70 |
+
"o9_gpu_hbm_temp_score",
|
| 71 |
+
"o9_thermal_delta_t_score",
|
| 72 |
+
"o9_cooling_flow_duty",
|
| 73 |
+
"o9_cooling_maintenance_active",
|
| 74 |
+
"o9_thermal_throttle_support_score",
|
| 75 |
+
"o10_world_size",
|
| 76 |
+
"o10_runtime_framework_class",
|
| 77 |
+
"o10_rank_stability_score",
|
| 78 |
+
"o10_same_image_gpu_count",
|
| 79 |
+
"o10_rendezvous_present",
|
| 80 |
+
"o10_runtime_metadata_confidence",
|
| 81 |
+
"o10_declared_vs_observed_mismatch_score",
|
| 82 |
+
"o11_data_staging_tb",
|
| 83 |
+
"o11_checkpoint_write_tb_per_event",
|
| 84 |
+
"o11_checkpoint_periodicity_score",
|
| 85 |
+
"o11_read_write_training_pattern_score",
|
| 86 |
+
"o11_checkpoint_jitter_score",
|
| 87 |
+
"o11_artifact_write_pattern_score",
|
| 88 |
+
"o11_dataloader_read_pattern_score",
|
| 89 |
+
"o11_backup_or_replication_pattern_score",
|
| 90 |
+
"o11_storage_cotraffic_score",
|
| 91 |
+
"o12_signed_ml_logs_present",
|
| 92 |
+
"o12_declared_parameter_count_b",
|
| 93 |
+
"o12_training_tokens_b",
|
| 94 |
+
"o12_step_count",
|
| 95 |
+
"o12_loss_curve_present",
|
| 96 |
+
"o12_optimizer_state_present",
|
| 97 |
+
"o12_log_delivery_delay_hours",
|
| 98 |
+
"o12_log_completeness_fraction",
|
| 99 |
+
"o12_declaration_consistency_score",
|
| 100 |
+
"o13_attestation_valid_fraction",
|
| 101 |
+
"o13_confidential_compute_mode_fraction",
|
| 102 |
+
"o13_collector_measurement_valid",
|
| 103 |
+
"o14_min_critical_coverage",
|
| 104 |
+
"o14_gap_fraction_critical",
|
| 105 |
+
"o14_clock_drift_max_ms",
|
| 106 |
+
"o14_counter_reset_count",
|
| 107 |
+
"o15_unapproved_physical_change_near_window",
|
| 108 |
+
"o15_firmware_bmc_change_near_window",
|
| 109 |
+
"o16_probe_throughput_ratio_min",
|
| 110 |
+
"o16_probe_latency_inflation_max",
|
| 111 |
+
"o16_vram_residency_conflict_score",
|
| 112 |
+
"o17_external_capacity_assertion",
|
| 113 |
+
"o17_energy_contract_alignment_score",
|
| 114 |
+
"o17_network_provider_utilization_score",
|
| 115 |
+
"o17_procurement_or_maintenance_explanation_score",
|
| 116 |
+
"o1_coverage_fraction",
|
| 117 |
+
"o1_missing_reason",
|
| 118 |
+
"o2_coverage_fraction",
|
| 119 |
+
"o2_missing_reason",
|
| 120 |
+
"o3_coverage_fraction",
|
| 121 |
+
"o3_missing_reason",
|
| 122 |
+
"o4_coverage_fraction",
|
| 123 |
+
"o4_missing_reason",
|
| 124 |
+
"o5_coverage_fraction",
|
| 125 |
+
"o5_missing_reason",
|
| 126 |
+
"o6_coverage_fraction",
|
| 127 |
+
"o6_missing_reason",
|
| 128 |
+
"o7_coverage_fraction",
|
| 129 |
+
"o7_missing_reason",
|
| 130 |
+
"o8_coverage_fraction",
|
| 131 |
+
"o8_missing_reason",
|
| 132 |
+
"o9_coverage_fraction",
|
| 133 |
+
"o9_missing_reason",
|
| 134 |
+
"o10_coverage_fraction",
|
| 135 |
+
"o10_missing_reason",
|
| 136 |
+
"o11_coverage_fraction",
|
| 137 |
+
"o11_missing_reason",
|
| 138 |
+
"o12_coverage_fraction",
|
| 139 |
+
"o12_missing_reason",
|
| 140 |
+
"o13_coverage_fraction",
|
| 141 |
+
"o13_missing_reason",
|
| 142 |
+
"o14_coverage_fraction",
|
| 143 |
+
"o14_missing_reason",
|
| 144 |
+
"o15_coverage_fraction",
|
| 145 |
+
"o15_missing_reason",
|
| 146 |
+
"o16_coverage_fraction",
|
| 147 |
+
"o16_missing_reason",
|
| 148 |
+
"o17_coverage_fraction",
|
| 149 |
+
"o17_missing_reason",
|
| 150 |
+
"scheduler_signature_status",
|
| 151 |
+
"gpu_telemetry_trust_level",
|
| 152 |
+
"fabric_telemetry_trust_level",
|
| 153 |
+
"power_meter_trust_level"
|
| 154 |
+
]
|
data/model_runs/synthetic_v1_baseline/feature_importance.csv
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
feature,importance_mean,importance_std,baseline_macro_f1,permuted_macro_f1_mean
|
| 2 |
+
o12_log_completeness_fraction,0.23359158430990315,0.005373690197882257,0.9781141424575879,0.7445225581476848
|
| 3 |
+
o2_allocation_duration_hours,0.14849344450367144,0.0052789099249230705,0.9781141424575879,0.8296206979539164
|
| 4 |
+
o11_checkpoint_periodicity_score,0.09666000121374829,0.0011059737197036232,0.9781141424575879,0.8814541412438396
|
| 5 |
+
o14_min_critical_coverage,0.08425447962391323,0.0028386015672915883,0.9781141424575879,0.8938596628336746
|
| 6 |
+
o7_inference_fanout_score,0.023026962271348744,0.000352376812149185,0.9781141424575879,0.9550871801862391
|
| 7 |
+
o7_synchronized_fabric_footprint,0.0216681347660882,0.0031004413375968374,0.9781141424575879,0.9564460076914997
|
| 8 |
+
policy_compute_ratio,0.01888468408039194,0.002076870274795479,0.9781141424575879,0.959229458377196
|
| 9 |
+
o9_thermal_delta_t_score,0.015522644977829625,0.0017361645292459213,0.9781141424575879,0.9625914974797581
|
| 10 |
+
o8_rack_power_fraction_p95,0.013580628502308012,0.0017886779558558932,0.9781141424575879,0.9645335139552799
|
| 11 |
+
o8_power_cv,0.010049244358529422,0.0007442207607013514,0.9781141424575879,0.9680648980990584
|
| 12 |
+
o2_max_concurrent_normalized_gpus,0.00956190440682921,0.0005897138759531066,0.9781141424575879,0.9685522380507586
|
| 13 |
+
o10_runtime_framework_class,0.009123255221792212,0.0002556343603386503,0.9781141424575879,0.9689908872357957
|
| 14 |
+
o2_topology_contiguity_score,0.008465602515600726,0.000978973878068464,0.9781141424575879,0.9696485399419871
|
| 15 |
+
o4_gpu_idle_gap_p95_minutes,0.008401230772478193,0.0007671011073014647,0.9781141424575879,0.9697129116851096
|
| 16 |
+
o8_power_to_gpu_residual,0.006467490611914021,0.0012204702714004524,0.9781141424575879,0.9716466518456738
|
| 17 |
+
o11_data_staging_tb,0.004774385170709716,0.0011077200740331708,0.9781141424575879,0.9733397572868782
|
| 18 |
+
o4_gpu_util_cv,0.004515834867171553,0.00012814093735156415,0.9781141424575879,0.9735983075904163
|
| 19 |
+
o11_dataloader_read_pattern_score,0.003564301181372668,0.00039712138629140767,0.9781141424575879,0.9745498412762151
|
| 20 |
+
o7_collective_jitter_score,0.003338909258623716,0.00025581495381753373,0.9781141424575879,0.9747752331989642
|
| 21 |
+
o7_collective_periodicity_score,0.0022545386038181534,0.0006384080942919812,0.9781141424575879,0.9758596038537698
|
| 22 |
+
o4_hbm_bandwidth_active_p95,0.0021022174553009254,0.0008926825404043516,0.9781141424575879,0.976011925002287
|
| 23 |
+
o7_burst_duty_cycle,0.001712656669582091,0.0003376920900513128,0.9781141424575879,0.9764014857880058
|
| 24 |
+
o11_read_write_training_pattern_score,0.0014772421580005284,0.0007389848391526284,0.9781141424575879,0.9766369002995873
|
| 25 |
+
o9_gpu_hbm_temp_score,0.001261581096622888,0.0003374158130753862,0.9781141424575879,0.976852561360965
|
| 26 |
+
o7_cross_section_sync_score,0.0012615470192883043,0.0002551176083138864,0.9781141424575879,0.9768525954382996
|
| 27 |
+
o7_coverage_fraction,0.0009957627772852657,0.0002559712480885151,0.9781141424575879,0.9771183796803026
|
| 28 |
+
o4_gpu_power_fraction_p95,0.0009910051757533085,0.0001274863360027242,0.9781141424575879,0.9771231372818345
|
| 29 |
+
o2_concurrency_fraction_domain,0.0009705866111717754,0.0004495525752025081,0.9781141424575879,0.9771435558464162
|
| 30 |
+
o4_coverage_fraction,0.0008706275099251334,0.0002696962800440298,0.9781141424575879,0.9772435149476628
|
| 31 |
+
o7_flow_entropy_score,0.0008107122703610026,0.0,0.9781141424575879,0.9773034301872269
|
| 32 |
+
o11_backup_or_replication_pattern_score,0.0006612636677401454,0.0002701885364253015,0.9781141424575879,0.9774528787898477
|
| 33 |
+
o4_hbm_used_fraction_p50,0.0006554675510399935,0.0004944170097273767,0.9781141424575879,0.9774586749065479
|
| 34 |
+
o5_coverage_fraction,0.0005406101441621051,0.0005840293050570898,0.9781141424575879,0.9775735323134258
|
| 35 |
+
o7_storage_traffic_fraction,0.0002716123882583095,0.00022176553850800082,0.9781141424575879,0.9778425300693296
|
| 36 |
+
o7_account_flow_linkage_confidence,0.00011013595622607249,0.00015575576299984125,0.9781141424575879,0.9780040065013619
|
| 37 |
+
o2_declared_workload_class,6.777792644607761e-05,0.0002861792488526281,0.9781141424575879,0.9780463645311418
|
| 38 |
+
capacity_possible,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 39 |
+
fabric_telemetry_trust_level,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 40 |
+
gpu_telemetry_trust_level,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 41 |
+
o10_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 42 |
+
o10_declared_vs_observed_mismatch_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 43 |
+
o10_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 44 |
+
o10_rank_stability_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 45 |
+
o10_rendezvous_present,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 46 |
+
o10_runtime_metadata_confidence,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 47 |
+
o10_same_image_gpu_count,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 48 |
+
o10_world_size,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 49 |
+
o11_artifact_write_pattern_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 50 |
+
o11_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 51 |
+
o11_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 52 |
+
o11_storage_cotraffic_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 53 |
+
o12_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 54 |
+
o12_declaration_consistency_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 55 |
+
o12_declared_parameter_count_b,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 56 |
+
o12_log_delivery_delay_hours,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 57 |
+
o12_loss_curve_present,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 58 |
+
o12_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 59 |
+
o12_optimizer_state_present,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 60 |
+
o12_signed_ml_logs_present,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 61 |
+
o12_step_count,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 62 |
+
o12_training_tokens_b,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 63 |
+
o13_attestation_valid_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 64 |
+
o13_collector_measurement_valid,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 65 |
+
o13_confidential_compute_mode_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 66 |
+
o13_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 67 |
+
o13_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 68 |
+
o14_counter_reset_count,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 69 |
+
o14_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 70 |
+
o14_gap_fraction_critical,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 71 |
+
o14_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 72 |
+
o15_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 73 |
+
o15_firmware_bmc_change_near_window,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 74 |
+
o15_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 75 |
+
o15_unapproved_physical_change_near_window,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 76 |
+
o16_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 77 |
+
o16_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 78 |
+
o16_probe_latency_inflation_max,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 79 |
+
o16_probe_throughput_ratio_min,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 80 |
+
o16_vram_residency_conflict_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 81 |
+
o17_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 82 |
+
o17_energy_contract_alignment_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 83 |
+
o17_external_capacity_assertion,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 84 |
+
o17_external_capacity_conflict_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 85 |
+
o17_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 86 |
+
o17_network_provider_utilization_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 87 |
+
o17_procurement_or_maintenance_explanation_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 88 |
+
o1_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 89 |
+
o1_homogeneous_high_end_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 90 |
+
o1_inventory_delta_rate,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 91 |
+
o1_largest_contiguous_domain_gpus,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 92 |
+
o1_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 93 |
+
o1_non_partitioned_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 94 |
+
o1_normalized_h100e_capacity,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 95 |
+
o2_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 96 |
+
o2_elastic_resize_count,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 97 |
+
o2_gpu_hours_policy_ratio,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 98 |
+
o2_job_array_width,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 99 |
+
o2_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 100 |
+
o2_preemption_restart_count,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 101 |
+
o2_reservation_exclusive_flag,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 102 |
+
o2_reservation_reuse_count,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 103 |
+
o2_scheduler_queue_delay_hours,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 104 |
+
o3_batch_provisioned_gpus,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 105 |
+
o3_billing_continuity_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 106 |
+
o3_capacity_reservation_duration_hours,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 107 |
+
o3_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 108 |
+
o3_egress_tb,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 109 |
+
o3_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 110 |
+
o3_training_sku_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 111 |
+
o4_gpu_util_duty_gt_70,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 112 |
+
o4_gpu_util_p50,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 113 |
+
o4_gpu_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 114 |
+
o4_hbm_pressure_duration_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 115 |
+
o4_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 116 |
+
o4_power_cap_active_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 117 |
+
o4_sm_tensor_active_p95,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 118 |
+
o4_thermal_throttle_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 119 |
+
o5_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 120 |
+
o5_profiler_available,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 121 |
+
o5_tensor_throughput_ratio,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 122 |
+
o6_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 123 |
+
o6_link_error_spike_score,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 124 |
+
o6_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 125 |
+
o6_nvlink_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 126 |
+
o7_job_to_port_mapping_coverage,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 127 |
+
o7_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 128 |
+
o7_rdma_congestion_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 129 |
+
o7_scaleout_port_util_p95,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 130 |
+
o8_coverage_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 131 |
+
o8_facility_it_power_mw,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 132 |
+
o8_missing_reason,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 133 |
+
o8_power_baseline_drift_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 134 |
+
o8_power_cap_or_curtailment_active,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 135 |
+
o8_power_continuity_days,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 136 |
+
o8_unattributed_power_fraction,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 137 |
+
o9_cooling_flow_duty,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 138 |
+
o9_cooling_maintenance_active,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 139 |
+
o9_coverage_fraction,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 140 |
+
o9_missing_reason,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 141 |
+
o9_thermal_throttle_support_score,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 142 |
+
power_meter_trust_level,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 143 |
+
scheduler_signature_status,0.0,0.0,0.9781141424575879,0.9781141424575879
|
| 144 |
+
scope_type,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 145 |
+
window_length_seconds,0.0,0.0,0.9781141424575879,0.978114142457588
|
| 146 |
+
o4_error_spike_score,-0.000112313549517018,0.00015883534496522904,0.9781141424575879,0.9782264560071049
|
| 147 |
+
o5_kernel_training_motif_score,-0.000112313549517018,0.00015883534496522904,0.9781141424575879,0.9782264560071049
|
| 148 |
+
o2_account_linkage_confidence,-0.00022690538180129374,0.00016047059544896947,0.9781141424575879,0.9783410478393891
|
| 149 |
+
o11_checkpoint_jitter_score,-0.00024461273048711113,0.00041613647356600846,0.9781141424575879,0.9783587551880749
|
| 150 |
+
o11_checkpoint_write_tb_per_event,-0.0003526063865893307,0.00026759207342307376,0.9781141424575879,0.9784667488441773
|
| 151 |
+
o6_nvlink_periodicity_score,-0.0006442779638646812,0.0003427827988719719,0.9781141424575879,0.9787584204214527
|
| 152 |
+
o14_clock_drift_max_ms,-0.001169240694518477,0.0009162828683296031,0.9781141424575879,0.9792833831521065
|
| 153 |
+
o8_baseline_subtracted_energy_kwh,-0.0031618376880434562,0.0004309423062690692,0.9781141424575879,0.9812759801456313
|
data/model_runs/synthetic_v1_baseline/manifest.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifact_hashes": {
|
| 3 |
+
"README.md": "sha256:e1cca94649d1dd9f08cf2b1a772737fbd7362e3d24f0e5f49567f79fdced1130",
|
| 4 |
+
"calibration_metrics.json": "sha256:a1b673af40faa3ed6b614b7e3cf3753964ede2e3810bcd5354d6657153577897",
|
| 5 |
+
"classification_report.json": "sha256:c74bb9ef28966c94937dd3ddb30296937b8bca224dd8f6fcf781d680621eab0c",
|
| 6 |
+
"confusion_matrix.csv": "sha256:265fa42852a745220b1ea17ee8f502c1b5a24f74063a60f7de9acdd154a830af",
|
| 7 |
+
"evidence_audit_sample.csv": "sha256:a9efe77b1ccc902b0a8f28b2b8b0cb72c9524e39f0d21676b27bb20ec3ce683d",
|
| 8 |
+
"excluded_columns.json": "sha256:0811708d16c4a2b8444eb8324b378ea61cc7ad30931adc14312f2a62587c182b",
|
| 9 |
+
"feature_columns.json": "sha256:f8c411ac97a84df3e13f37163bfa210c2a4019208678b77d5131cb5579f3f976",
|
| 10 |
+
"feature_importance.csv": "sha256:96cb96808c2b5ad9e7a23ba08fc485ddb5e026eddbc54ffa8496e7bb8635c095",
|
| 11 |
+
"metrics.json": "sha256:d7d459e44ed94014a33fb39fbffca89deb69d40ec38658ffa703f009a511a317",
|
| 12 |
+
"model.joblib": "sha256:06c701b0d9c74cdfd0066fe3212013ad2393c3eca34ca625c78792631fa3f338",
|
| 13 |
+
"predictions_all.csv": "sha256:e2e66b8c5f743d13bb4a24b57656cf7f246cb423071efc1049a9f11019e8f549",
|
| 14 |
+
"predictions_test.csv": "sha256:e4fb8e249d438455967b6ff491d51e3db243002c326fe2f66c023f134c5160e1",
|
| 15 |
+
"preprocessing.joblib": "sha256:edf78f71a10335c7703a183a76b97add8ee6ecc8b0ca1068fdcdedab18bcf2de",
|
| 16 |
+
"split_manifest.json": "sha256:4ab606273d10f182b02a5154982a7943c0837b6e5725dee907c97c25e2daff92",
|
| 17 |
+
"validation_summary.md": "sha256:d3da3e62f5e957260990001209805854976b3b2dfe56b921104077a6395918c3"
|
| 18 |
+
},
|
| 19 |
+
"calibration_method": "sigmoid_on_validation_split_frozen_estimator",
|
| 20 |
+
"created_or_updated_at": "2026-05-11T00:23:31Z",
|
| 21 |
+
"features_path": "data/synthetic_v1/features/window_features_all.csv",
|
| 22 |
+
"limitations": [
|
| 23 |
+
"Trained on synthetic data only.",
|
| 24 |
+
"Performance numbers are not real-world deployment claims.",
|
| 25 |
+
"Episode-level group splitting is required because adjacent windows are correlated.",
|
| 26 |
+
"Synthetic labels encode generator assumptions.",
|
| 27 |
+
"Real deployment requires real telemetry calibration and controlled drills."
|
| 28 |
+
],
|
| 29 |
+
"metrics_summary": {
|
| 30 |
+
"accuracy": 0.9724612736660929,
|
| 31 |
+
"brier_large_training": 0.001846207706971921,
|
| 32 |
+
"ece_large_training": 0.010488907264903001,
|
| 33 |
+
"label_3_4_precision": 0.993517017828201,
|
| 34 |
+
"label_3_4_recall": 0.9967479674796748,
|
| 35 |
+
"macro_f1": 0.9781141424575879,
|
| 36 |
+
"p_large_training_precision_at_0_5": 0.993517017828201,
|
| 37 |
+
"p_large_training_recall_at_0_5": 0.9967479674796748,
|
| 38 |
+
"weighted_f1": 0.9722899614011162
|
| 39 |
+
},
|
| 40 |
+
"model_run_id": "synthetic_v1_baseline",
|
| 41 |
+
"model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
|
| 42 |
+
"required_outputs": [
|
| 43 |
+
"README.md",
|
| 44 |
+
"manifest.json",
|
| 45 |
+
"model.joblib",
|
| 46 |
+
"preprocessing.joblib",
|
| 47 |
+
"feature_columns.json",
|
| 48 |
+
"excluded_columns.json",
|
| 49 |
+
"split_manifest.json",
|
| 50 |
+
"metrics.json",
|
| 51 |
+
"calibration_metrics.json",
|
| 52 |
+
"confusion_matrix.csv",
|
| 53 |
+
"classification_report.json",
|
| 54 |
+
"predictions_test.csv",
|
| 55 |
+
"predictions_all.csv",
|
| 56 |
+
"feature_importance.csv",
|
| 57 |
+
"evidence_audit_sample.csv",
|
| 58 |
+
"validation_summary.md"
|
| 59 |
+
],
|
| 60 |
+
"training_metadata": {
|
| 61 |
+
"calibration_method": "sigmoid_on_validation_split_frozen_estimator",
|
| 62 |
+
"dataset_validation_status": [
|
| 63 |
+
{
|
| 64 |
+
"command": "/Users/ifc24/miniforge3/bin/python /Users/ifc24/Develop/datacenter-verification/src/datacenter_verification_synthetic/validate_synthetic_dataset.py --dataset data/synthetic_v1",
|
| 65 |
+
"name": "synthetic_dataset_validator",
|
| 66 |
+
"returncode": 0,
|
| 67 |
+
"stderr": "",
|
| 68 |
+
"stdout": "PASS\nraw_records: 152457\nfeature_rows: 14712\nlabel_distribution: {0: 3861, 1: 3333, 2: 4203, 3: 2489, 4: 826}"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"command": "/Users/ifc24/miniforge3/bin/python -m src.datacenter_verification_validators --dataset data/synthetic_v1",
|
| 72 |
+
"name": "public_dataset_validator",
|
| 73 |
+
"returncode": 0,
|
| 74 |
+
"stderr": "",
|
| 75 |
+
"stdout": "Validated dataset: data/synthetic_v1\nErrors: 0\nWarnings: 0\nReport: data/synthetic_v1/validation/validation_report.md"
|
| 76 |
+
}
|
| 77 |
+
],
|
| 78 |
+
"feature_count": 152,
|
| 79 |
+
"features_path": "data/synthetic_v1/features/window_features_all.csv",
|
| 80 |
+
"model_parameters": {
|
| 81 |
+
"categorical_features": "from_dtype",
|
| 82 |
+
"class_weight": "balanced",
|
| 83 |
+
"early_stopping": true,
|
| 84 |
+
"interaction_cst": null,
|
| 85 |
+
"l2_regularization": 0.03,
|
| 86 |
+
"learning_rate": 0.05,
|
| 87 |
+
"loss": "log_loss",
|
| 88 |
+
"max_bins": 255,
|
| 89 |
+
"max_depth": null,
|
| 90 |
+
"max_features": 1.0,
|
| 91 |
+
"max_iter": 350,
|
| 92 |
+
"max_leaf_nodes": 31,
|
| 93 |
+
"min_samples_leaf": 20,
|
| 94 |
+
"monotonic_cst": null,
|
| 95 |
+
"n_iter_no_change": 10,
|
| 96 |
+
"random_state": 20260510,
|
| 97 |
+
"scoring": "loss",
|
| 98 |
+
"tol": 1e-07,
|
| 99 |
+
"validation_fraction": 0.15,
|
| 100 |
+
"verbose": 0,
|
| 101 |
+
"warm_start": false
|
| 102 |
+
},
|
| 103 |
+
"model_type": "HistGradientBoostingClassifier",
|
| 104 |
+
"seed": 20260510,
|
| 105 |
+
"train_episodes": 458,
|
| 106 |
+
"train_rows": 8870,
|
| 107 |
+
"trained_at": "2026-05-11T00:21:12Z",
|
| 108 |
+
"validation_episodes": 152,
|
| 109 |
+
"validation_rows": 2937
|
| 110 |
+
},
|
| 111 |
+
"validation_status": [
|
| 112 |
+
{
|
| 113 |
+
"command": "/Users/ifc24/miniforge3/bin/python /Users/ifc24/Develop/datacenter-verification/src/datacenter_verification_synthetic/validate_synthetic_dataset.py --dataset data/synthetic_v1",
|
| 114 |
+
"name": "synthetic_dataset_validator",
|
| 115 |
+
"returncode": 0,
|
| 116 |
+
"stderr": "",
|
| 117 |
+
"stdout": "PASS\nraw_records: 152457\nfeature_rows: 14712\nlabel_distribution: {0: 3861, 1: 3333, 2: 4203, 3: 2489, 4: 826}"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"command": "/Users/ifc24/miniforge3/bin/python -m src.datacenter_verification_validators --dataset data/synthetic_v1",
|
| 121 |
+
"name": "public_dataset_validator",
|
| 122 |
+
"returncode": 0,
|
| 123 |
+
"stderr": "",
|
| 124 |
+
"stdout": "Validated dataset: data/synthetic_v1\nErrors: 0\nWarnings: 0\nReport: data/synthetic_v1/validation/validation_report.md"
|
| 125 |
+
}
|
| 126 |
+
]
|
| 127 |
+
}
|
data/model_runs/synthetic_v1_baseline/metrics.json
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"calibration": {
|
| 3 |
+
"brier_large_training": 0.001846207706971921,
|
| 4 |
+
"expected_calibration_error_large_training": 0.010488907264903001,
|
| 5 |
+
"log_loss": 0.10567154528993643
|
| 6 |
+
},
|
| 7 |
+
"dataset": {
|
| 8 |
+
"test_episodes": 150,
|
| 9 |
+
"test_label_distribution": {
|
| 10 |
+
"0": 762,
|
| 11 |
+
"1": 690,
|
| 12 |
+
"2": 838,
|
| 13 |
+
"3": 463,
|
| 14 |
+
"4": 152
|
| 15 |
+
},
|
| 16 |
+
"test_rows": 2905
|
| 17 |
+
},
|
| 18 |
+
"governance": {
|
| 19 |
+
"false_negative_scenarios_at_0_5": {
|
| 20 |
+
"fragmented_training_linked": 2
|
| 21 |
+
},
|
| 22 |
+
"false_positive_scenarios_at_0_5": {
|
| 23 |
+
"underclocked_energy_capped_training": 4
|
| 24 |
+
},
|
| 25 |
+
"label_0_missed_under_high_coverage": {
|
| 26 |
+
"missed_rate": 0.0,
|
| 27 |
+
"missed_rows": 0,
|
| 28 |
+
"rows": 762
|
| 29 |
+
},
|
| 30 |
+
"label_3_4_predicted_label": {
|
| 31 |
+
"f1": 0.9951298701298701,
|
| 32 |
+
"precision": 0.993517017828201,
|
| 33 |
+
"recall": 0.9967479674796748
|
| 34 |
+
},
|
| 35 |
+
"p_large_training_threshold_0_5": {
|
| 36 |
+
"f1": 0.9951298701298701,
|
| 37 |
+
"precision": 0.993517017828201,
|
| 38 |
+
"recall": 0.9967479674796748
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
"model": {
|
| 42 |
+
"accuracy": 0.9724612736660929,
|
| 43 |
+
"brier_multiclass_mean": 0.010624855064864712,
|
| 44 |
+
"log_loss": 0.10567154528993643,
|
| 45 |
+
"macro_f1": 0.9781141424575879,
|
| 46 |
+
"weighted_f1": 0.9722899614011162
|
| 47 |
+
},
|
| 48 |
+
"rule_baseline": {
|
| 49 |
+
"accuracy": 0.42857142857142855,
|
| 50 |
+
"label_3_4_predicted_label": {
|
| 51 |
+
"f1": 0.8418891170431212,
|
| 52 |
+
"precision": 0.7269503546099291,
|
| 53 |
+
"recall": 1.0
|
| 54 |
+
},
|
| 55 |
+
"macro_f1": 0.36363847151518797,
|
| 56 |
+
"weighted_f1": 0.3650992917466867
|
| 57 |
+
},
|
| 58 |
+
"subgroup_metrics": {
|
| 59 |
+
"capacity_possible": [
|
| 60 |
+
{
|
| 61 |
+
"accuracy": 0.9724612736660929,
|
| 62 |
+
"group": "True",
|
| 63 |
+
"large_training_false_negatives": 2,
|
| 64 |
+
"large_training_false_positives": 4,
|
| 65 |
+
"large_training_precision_at_0_5": 0.993517017828201,
|
| 66 |
+
"large_training_recall_at_0_5": 0.9967479674796748,
|
| 67 |
+
"macro_f1": 0.9781141424575879,
|
| 68 |
+
"rows": 2905
|
| 69 |
+
}
|
| 70 |
+
],
|
| 71 |
+
"data_quality_regime": [
|
| 72 |
+
{
|
| 73 |
+
"accuracy": 0.9742895805142084,
|
| 74 |
+
"group": "full_observability",
|
| 75 |
+
"large_training_false_negatives": 2,
|
| 76 |
+
"large_training_false_positives": 0,
|
| 77 |
+
"large_training_precision_at_0_5": 1.0,
|
| 78 |
+
"large_training_recall_at_0_5": 0.995249406175772,
|
| 79 |
+
"macro_f1": 0.97854699410172,
|
| 80 |
+
"rows": 2217
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"accuracy": 1.0,
|
| 84 |
+
"group": "routine_privacy_redaction",
|
| 85 |
+
"large_training_false_negatives": 0,
|
| 86 |
+
"large_training_false_positives": 0,
|
| 87 |
+
"large_training_precision_at_0_5": 1.0,
|
| 88 |
+
"large_training_recall_at_0_5": 1.0,
|
| 89 |
+
"macro_f1": 0.4,
|
| 90 |
+
"rows": 126
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"accuracy": 1.0,
|
| 94 |
+
"group": "maintenance_observability_loss",
|
| 95 |
+
"large_training_false_negatives": 0,
|
| 96 |
+
"large_training_false_positives": 0,
|
| 97 |
+
"large_training_precision_at_0_5": 0.0,
|
| 98 |
+
"large_training_recall_at_0_5": 0.0,
|
| 99 |
+
"macro_f1": 0.4,
|
| 100 |
+
"rows": 112
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"accuracy": 1.0,
|
| 104 |
+
"group": "delayed_log_delivery",
|
| 105 |
+
"large_training_false_negatives": 0,
|
| 106 |
+
"large_training_false_positives": 0,
|
| 107 |
+
"large_training_precision_at_0_5": 1.0,
|
| 108 |
+
"large_training_recall_at_0_5": 1.0,
|
| 109 |
+
"macro_f1": 0.6,
|
| 110 |
+
"rows": 111
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"accuracy": 0.8020833333333334,
|
| 114 |
+
"group": "collector_gap_long",
|
| 115 |
+
"large_training_false_negatives": 0,
|
| 116 |
+
"large_training_false_positives": 0,
|
| 117 |
+
"large_training_precision_at_0_5": 0.0,
|
| 118 |
+
"large_training_recall_at_0_5": 0.0,
|
| 119 |
+
"macro_f1": 0.31288358208955225,
|
| 120 |
+
"rows": 96
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"accuracy": 0.9393939393939394,
|
| 124 |
+
"group": "confidential_compute_counters_disabled",
|
| 125 |
+
"large_training_false_negatives": 0,
|
| 126 |
+
"large_training_false_positives": 4,
|
| 127 |
+
"large_training_precision_at_0_5": 0.0,
|
| 128 |
+
"large_training_recall_at_0_5": 0.0,
|
| 129 |
+
"macro_f1": 0.19375,
|
| 130 |
+
"rows": 66
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"accuracy": 1.0,
|
| 134 |
+
"group": "source_not_deployed_for_site",
|
| 135 |
+
"large_training_false_negatives": 0,
|
| 136 |
+
"large_training_false_positives": 0,
|
| 137 |
+
"large_training_precision_at_0_5": 1.0,
|
| 138 |
+
"large_training_recall_at_0_5": 1.0,
|
| 139 |
+
"macro_f1": 0.6,
|
| 140 |
+
"rows": 62
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"accuracy": 1.0,
|
| 144 |
+
"group": "partial_fabric_mapping",
|
| 145 |
+
"large_training_false_negatives": 0,
|
| 146 |
+
"large_training_false_positives": 0,
|
| 147 |
+
"large_training_precision_at_0_5": 0.0,
|
| 148 |
+
"large_training_recall_at_0_5": 0.0,
|
| 149 |
+
"macro_f1": 0.4,
|
| 150 |
+
"rows": 55
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"accuracy": 1.0,
|
| 154 |
+
"group": "collector_gap_short",
|
| 155 |
+
"large_training_false_negatives": 0,
|
| 156 |
+
"large_training_false_positives": 0,
|
| 157 |
+
"large_training_precision_at_0_5": 0.0,
|
| 158 |
+
"large_training_recall_at_0_5": 0.0,
|
| 159 |
+
"macro_f1": 0.4,
|
| 160 |
+
"rows": 44
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"accuracy": 1.0,
|
| 164 |
+
"group": "clock_drift_episode",
|
| 165 |
+
"large_training_false_negatives": 0,
|
| 166 |
+
"large_training_false_positives": 0,
|
| 167 |
+
"large_training_precision_at_0_5": 0.0,
|
| 168 |
+
"large_training_recall_at_0_5": 0.0,
|
| 169 |
+
"macro_f1": 0.2,
|
| 170 |
+
"rows": 16
|
| 171 |
+
}
|
| 172 |
+
],
|
| 173 |
+
"integrity_warning": [
|
| 174 |
+
{
|
| 175 |
+
"accuracy": 0.9765335529024289,
|
| 176 |
+
"group": "False",
|
| 177 |
+
"large_training_false_negatives": 2,
|
| 178 |
+
"large_training_false_positives": 0,
|
| 179 |
+
"large_training_precision_at_0_5": 1.0,
|
| 180 |
+
"large_training_recall_at_0_5": 0.9967479674796748,
|
| 181 |
+
"macro_f1": 0.978471713929574,
|
| 182 |
+
"rows": 2429
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"accuracy": 0.9516806722689075,
|
| 186 |
+
"group": "True",
|
| 187 |
+
"large_training_false_negatives": 0,
|
| 188 |
+
"large_training_false_positives": 4,
|
| 189 |
+
"large_training_precision_at_0_5": 0.0,
|
| 190 |
+
"large_training_recall_at_0_5": 0.0,
|
| 191 |
+
"macro_f1": 0.5816990094283327,
|
| 192 |
+
"rows": 476
|
| 193 |
+
}
|
| 194 |
+
],
|
| 195 |
+
"latent_workload_class": [
|
| 196 |
+
{
|
| 197 |
+
"accuracy": 1.0,
|
| 198 |
+
"group": "idle_or_low_activity",
|
| 199 |
+
"large_training_false_negatives": 0,
|
| 200 |
+
"large_training_false_positives": 0,
|
| 201 |
+
"large_training_precision_at_0_5": 0.0,
|
| 202 |
+
"large_training_recall_at_0_5": 0.0,
|
| 203 |
+
"macro_f1": 0.4,
|
| 204 |
+
"rows": 807
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"accuracy": 1.0,
|
| 208 |
+
"group": "hpc_mpi_collective",
|
| 209 |
+
"large_training_false_negatives": 0,
|
| 210 |
+
"large_training_false_positives": 0,
|
| 211 |
+
"large_training_precision_at_0_5": 0.0,
|
| 212 |
+
"large_training_recall_at_0_5": 0.0,
|
| 213 |
+
"macro_f1": 0.2,
|
| 214 |
+
"rows": 171
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
"accuracy": 0.9872611464968153,
|
| 218 |
+
"group": "fragmented_training_linked",
|
| 219 |
+
"large_training_false_negatives": 2,
|
| 220 |
+
"large_training_false_positives": 0,
|
| 221 |
+
"large_training_precision_at_0_5": 1.0,
|
| 222 |
+
"large_training_recall_at_0_5": 0.9672131147540983,
|
| 223 |
+
"macro_f1": 0.3946048109965636,
|
| 224 |
+
"rows": 157
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"accuracy": 1.0,
|
| 228 |
+
"group": "training_without_semantic_logs",
|
| 229 |
+
"large_training_false_negatives": 0,
|
| 230 |
+
"large_training_false_positives": 0,
|
| 231 |
+
"large_training_precision_at_0_5": 1.0,
|
| 232 |
+
"large_training_recall_at_0_5": 1.0,
|
| 233 |
+
"macro_f1": 0.4,
|
| 234 |
+
"rows": 144
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"accuracy": 1.0,
|
| 238 |
+
"group": "large_etl_or_data_movement",
|
| 239 |
+
"large_training_false_negatives": 0,
|
| 240 |
+
"large_training_false_positives": 0,
|
| 241 |
+
"large_training_precision_at_0_5": 0.0,
|
| 242 |
+
"large_training_recall_at_0_5": 0.0,
|
| 243 |
+
"macro_f1": 0.2,
|
| 244 |
+
"rows": 136
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"accuracy": 1.0,
|
| 248 |
+
"group": "reserved_but_unused_capacity",
|
| 249 |
+
"large_training_false_negatives": 0,
|
| 250 |
+
"large_training_false_positives": 0,
|
| 251 |
+
"large_training_precision_at_0_5": 0.0,
|
| 252 |
+
"large_training_recall_at_0_5": 0.0,
|
| 253 |
+
"macro_f1": 0.2,
|
| 254 |
+
"rows": 122
|
| 255 |
+
},
|
| 256 |
+
{
|
| 257 |
+
"accuracy": 1.0,
|
| 258 |
+
"group": "model_parallel_inference",
|
| 259 |
+
"large_training_false_negatives": 0,
|
| 260 |
+
"large_training_false_positives": 0,
|
| 261 |
+
"large_training_precision_at_0_5": 0.0,
|
| 262 |
+
"large_training_recall_at_0_5": 0.0,
|
| 263 |
+
"macro_f1": 0.4,
|
| 264 |
+
"rows": 104
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"accuracy": 1.0,
|
| 268 |
+
"group": "training_with_low_fabric_high_checkpoint",
|
| 269 |
+
"large_training_false_negatives": 0,
|
| 270 |
+
"large_training_false_positives": 0,
|
| 271 |
+
"large_training_precision_at_0_5": 1.0,
|
| 272 |
+
"large_training_recall_at_0_5": 1.0,
|
| 273 |
+
"macro_f1": 0.4,
|
| 274 |
+
"rows": 104
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"accuracy": 1.0,
|
| 278 |
+
"group": "hardware_burn_in_or_thermal_soak",
|
| 279 |
+
"large_training_false_negatives": 0,
|
| 280 |
+
"large_training_false_positives": 0,
|
| 281 |
+
"large_training_precision_at_0_5": 0.0,
|
| 282 |
+
"large_training_recall_at_0_5": 0.0,
|
| 283 |
+
"macro_f1": 0.2,
|
| 284 |
+
"rows": 103
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"accuracy": 0.6534653465346535,
|
| 288 |
+
"group": "normal_inference",
|
| 289 |
+
"large_training_false_negatives": 0,
|
| 290 |
+
"large_training_false_positives": 0,
|
| 291 |
+
"large_training_precision_at_0_5": 0.0,
|
| 292 |
+
"large_training_recall_at_0_5": 0.0,
|
| 293 |
+
"macro_f1": 0.277555239695051,
|
| 294 |
+
"rows": 101
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"accuracy": 0.7956989247311828,
|
| 298 |
+
"group": "large_batch_inference",
|
| 299 |
+
"large_training_false_negatives": 0,
|
| 300 |
+
"large_training_false_positives": 0,
|
| 301 |
+
"large_training_precision_at_0_5": 0.0,
|
| 302 |
+
"large_training_recall_at_0_5": 0.0,
|
| 303 |
+
"macro_f1": 0.3176703552708212,
|
| 304 |
+
"rows": 93
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"accuracy": 1.0,
|
| 308 |
+
"group": "training_with_delayed_logs",
|
| 309 |
+
"large_training_false_negatives": 0,
|
| 310 |
+
"large_training_false_positives": 0,
|
| 311 |
+
"large_training_precision_at_0_5": 1.0,
|
| 312 |
+
"large_training_recall_at_0_5": 1.0,
|
| 313 |
+
"macro_f1": 0.6,
|
| 314 |
+
"rows": 91
|
| 315 |
+
},
|
| 316 |
+
{
|
| 317 |
+
"accuracy": 0.9550561797752809,
|
| 318 |
+
"group": "underclocked_energy_capped_training",
|
| 319 |
+
"large_training_false_negatives": 0,
|
| 320 |
+
"large_training_false_positives": 4,
|
| 321 |
+
"large_training_precision_at_0_5": 0.9384615384615385,
|
| 322 |
+
"large_training_recall_at_0_5": 1.0,
|
| 323 |
+
"macro_f1": 0.37826617826617825,
|
| 324 |
+
"rows": 89
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"accuracy": 1.0,
|
| 328 |
+
"group": "capacity_or_integrity_only_warning",
|
| 329 |
+
"large_training_false_negatives": 0,
|
| 330 |
+
"large_training_false_positives": 0,
|
| 331 |
+
"large_training_precision_at_0_5": 0.0,
|
| 332 |
+
"large_training_recall_at_0_5": 0.0,
|
| 333 |
+
"macro_f1": 0.2,
|
| 334 |
+
"rows": 87
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"accuracy": 1.0,
|
| 338 |
+
"group": "cloud_training_redacted_runtime",
|
| 339 |
+
"large_training_false_negatives": 0,
|
| 340 |
+
"large_training_false_positives": 0,
|
| 341 |
+
"large_training_precision_at_0_5": 1.0,
|
| 342 |
+
"large_training_recall_at_0_5": 1.0,
|
| 343 |
+
"macro_f1": 0.4,
|
| 344 |
+
"rows": 86
|
| 345 |
+
},
|
| 346 |
+
{
|
| 347 |
+
"accuracy": 1.0,
|
| 348 |
+
"group": "pretraining_standard",
|
| 349 |
+
"large_training_false_negatives": 0,
|
| 350 |
+
"large_training_false_positives": 0,
|
| 351 |
+
"large_training_precision_at_0_5": 1.0,
|
| 352 |
+
"large_training_recall_at_0_5": 1.0,
|
| 353 |
+
"macro_f1": 0.2,
|
| 354 |
+
"rows": 86
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"accuracy": 1.0,
|
| 358 |
+
"group": "multi_tenant_fragmented_nontraining",
|
| 359 |
+
"large_training_false_negatives": 0,
|
| 360 |
+
"large_training_false_positives": 0,
|
| 361 |
+
"large_training_precision_at_0_5": 0.0,
|
| 362 |
+
"large_training_recall_at_0_5": 0.0,
|
| 363 |
+
"macro_f1": 0.2,
|
| 364 |
+
"rows": 84
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"accuracy": 1.0,
|
| 368 |
+
"group": "storage_rebuild_or_replication",
|
| 369 |
+
"large_training_false_negatives": 0,
|
| 370 |
+
"large_training_false_positives": 0,
|
| 371 |
+
"large_training_precision_at_0_5": 0.0,
|
| 372 |
+
"large_training_recall_at_0_5": 0.0,
|
| 373 |
+
"macro_f1": 0.4,
|
| 374 |
+
"rows": 82
|
| 375 |
+
},
|
| 376 |
+
{
|
| 377 |
+
"accuracy": 1.0,
|
| 378 |
+
"group": "nccl_extended_benchmark",
|
| 379 |
+
"large_training_false_negatives": 0,
|
| 380 |
+
"large_training_false_positives": 0,
|
| 381 |
+
"large_training_precision_at_0_5": 0.0,
|
| 382 |
+
"large_training_recall_at_0_5": 0.0,
|
| 383 |
+
"macro_f1": 0.2,
|
| 384 |
+
"rows": 69
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
"accuracy": 0.6296296296296297,
|
| 388 |
+
"group": "embedding_generation",
|
| 389 |
+
"large_training_false_negatives": 0,
|
| 390 |
+
"large_training_false_positives": 0,
|
| 391 |
+
"large_training_precision_at_0_5": 0.0,
|
| 392 |
+
"large_training_recall_at_0_5": 0.0,
|
| 393 |
+
"macro_f1": 0.15454545454545454,
|
| 394 |
+
"rows": 54
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
"accuracy": 1.0,
|
| 398 |
+
"group": "large_fine_tune_standard",
|
| 399 |
+
"large_training_false_negatives": 0,
|
| 400 |
+
"large_training_false_positives": 0,
|
| 401 |
+
"large_training_precision_at_0_5": 1.0,
|
| 402 |
+
"large_training_recall_at_0_5": 1.0,
|
| 403 |
+
"macro_f1": 0.4,
|
| 404 |
+
"rows": 42
|
| 405 |
+
},
|
| 406 |
+
{
|
| 407 |
+
"accuracy": 1.0,
|
| 408 |
+
"group": "multi_stage_training_pipeline",
|
| 409 |
+
"large_training_false_negatives": 0,
|
| 410 |
+
"large_training_false_positives": 0,
|
| 411 |
+
"large_training_precision_at_0_5": 1.0,
|
| 412 |
+
"large_training_recall_at_0_5": 1.0,
|
| 413 |
+
"macro_f1": 0.4,
|
| 414 |
+
"rows": 24
|
| 415 |
+
},
|
| 416 |
+
{
|
| 417 |
+
"accuracy": 1.0,
|
| 418 |
+
"group": "counter_suppressed_candidate_window",
|
| 419 |
+
"large_training_false_negatives": 0,
|
| 420 |
+
"large_training_false_positives": 0,
|
| 421 |
+
"large_training_precision_at_0_5": 0.0,
|
| 422 |
+
"large_training_recall_at_0_5": 0.0,
|
| 423 |
+
"macro_f1": 0.2,
|
| 424 |
+
"rows": 22
|
| 425 |
+
},
|
| 426 |
+
{
|
| 427 |
+
"accuracy": 1.0,
|
| 428 |
+
"group": "distributed_database_or_graph_analytics",
|
| 429 |
+
"large_training_false_negatives": 0,
|
| 430 |
+
"large_training_false_positives": 0,
|
| 431 |
+
"large_training_precision_at_0_5": 0.0,
|
| 432 |
+
"large_training_recall_at_0_5": 0.0,
|
| 433 |
+
"macro_f1": 0.2,
|
| 434 |
+
"rows": 18
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
"accuracy": 1.0,
|
| 438 |
+
"group": "maintenance_with_collector_gaps",
|
| 439 |
+
"large_training_false_negatives": 0,
|
| 440 |
+
"large_training_false_positives": 0,
|
| 441 |
+
"large_training_precision_at_0_5": 0.0,
|
| 442 |
+
"large_training_recall_at_0_5": 0.0,
|
| 443 |
+
"macro_f1": 0.2,
|
| 444 |
+
"rows": 15
|
| 445 |
+
},
|
| 446 |
+
{
|
| 447 |
+
"accuracy": 1.0,
|
| 448 |
+
"group": "synthetic_data_generation_gpu_heavy",
|
| 449 |
+
"large_training_false_negatives": 0,
|
| 450 |
+
"large_training_false_positives": 0,
|
| 451 |
+
"large_training_precision_at_0_5": 0.0,
|
| 452 |
+
"large_training_recall_at_0_5": 0.0,
|
| 453 |
+
"macro_f1": 0.2,
|
| 454 |
+
"rows": 14
|
| 455 |
+
}
|
| 456 |
+
],
|
| 457 |
+
"o12_missing_reason": [
|
| 458 |
+
{
|
| 459 |
+
"accuracy": 0.968040370058873,
|
| 460 |
+
"group": "privacy_redacted",
|
| 461 |
+
"large_training_false_negatives": 2,
|
| 462 |
+
"large_training_false_positives": 0,
|
| 463 |
+
"large_training_precision_at_0_5": 1.0,
|
| 464 |
+
"large_training_recall_at_0_5": 0.991701244813278,
|
| 465 |
+
"macro_f1": 0.777350004737448,
|
| 466 |
+
"rows": 2378
|
| 467 |
+
},
|
| 468 |
+
{
|
| 469 |
+
"accuracy": 0.9913978494623656,
|
| 470 |
+
"group": "delayed_log_delivery",
|
| 471 |
+
"large_training_false_negatives": 0,
|
| 472 |
+
"large_training_false_positives": 4,
|
| 473 |
+
"large_training_precision_at_0_5": 0.9889196675900277,
|
| 474 |
+
"large_training_recall_at_0_5": 1.0,
|
| 475 |
+
"macro_f1": 0.5942940479445811,
|
| 476 |
+
"rows": 465
|
| 477 |
+
},
|
| 478 |
+
{
|
| 479 |
+
"accuracy": 1.0,
|
| 480 |
+
"group": "source_not_deployed",
|
| 481 |
+
"large_training_false_negatives": 0,
|
| 482 |
+
"large_training_false_positives": 0,
|
| 483 |
+
"large_training_precision_at_0_5": 1.0,
|
| 484 |
+
"large_training_recall_at_0_5": 1.0,
|
| 485 |
+
"macro_f1": 0.6,
|
| 486 |
+
"rows": 62
|
| 487 |
+
}
|
| 488 |
+
],
|
| 489 |
+
"o4_missing_reason": [
|
| 490 |
+
{
|
| 491 |
+
"accuracy": 0.9777950915465524,
|
| 492 |
+
"group": "observed",
|
| 493 |
+
"large_training_false_negatives": 2,
|
| 494 |
+
"large_training_false_positives": 0,
|
| 495 |
+
"large_training_precision_at_0_5": 1.0,
|
| 496 |
+
"large_training_recall_at_0_5": 0.9967479674796748,
|
| 497 |
+
"macro_f1": 0.9808162711257303,
|
| 498 |
+
"rows": 2567
|
| 499 |
+
},
|
| 500 |
+
{
|
| 501 |
+
"accuracy": 0.9301470588235294,
|
| 502 |
+
"group": "collector_gap",
|
| 503 |
+
"large_training_false_negatives": 0,
|
| 504 |
+
"large_training_false_positives": 0,
|
| 505 |
+
"large_training_precision_at_0_5": 0.0,
|
| 506 |
+
"large_training_recall_at_0_5": 0.0,
|
| 507 |
+
"macro_f1": 0.3720493787098257,
|
| 508 |
+
"rows": 272
|
| 509 |
+
},
|
| 510 |
+
{
|
| 511 |
+
"accuracy": 0.9393939393939394,
|
| 512 |
+
"group": "counter_disabled_by_cc_mode",
|
| 513 |
+
"large_training_false_negatives": 0,
|
| 514 |
+
"large_training_false_positives": 4,
|
| 515 |
+
"large_training_precision_at_0_5": 0.0,
|
| 516 |
+
"large_training_recall_at_0_5": 0.0,
|
| 517 |
+
"macro_f1": 0.19375,
|
| 518 |
+
"rows": 66
|
| 519 |
+
}
|
| 520 |
+
],
|
| 521 |
+
"o7_missing_reason": [
|
| 522 |
+
{
|
| 523 |
+
"accuracy": 0.9763382467028704,
|
| 524 |
+
"group": "observed",
|
| 525 |
+
"large_training_false_negatives": 2,
|
| 526 |
+
"large_training_false_positives": 4,
|
| 527 |
+
"large_training_precision_at_0_5": 0.993517017828201,
|
| 528 |
+
"large_training_recall_at_0_5": 0.9967479674796748,
|
| 529 |
+
"macro_f1": 0.9793065248203424,
|
| 530 |
+
"rows": 2578
|
| 531 |
+
},
|
| 532 |
+
{
|
| 533 |
+
"accuracy": 0.9301470588235294,
|
| 534 |
+
"group": "collector_gap",
|
| 535 |
+
"large_training_false_negatives": 0,
|
| 536 |
+
"large_training_false_positives": 0,
|
| 537 |
+
"large_training_precision_at_0_5": 0.0,
|
| 538 |
+
"large_training_recall_at_0_5": 0.0,
|
| 539 |
+
"macro_f1": 0.3720493787098257,
|
| 540 |
+
"rows": 272
|
| 541 |
+
},
|
| 542 |
+
{
|
| 543 |
+
"accuracy": 1.0,
|
| 544 |
+
"group": "privacy_redacted",
|
| 545 |
+
"large_training_false_negatives": 0,
|
| 546 |
+
"large_training_false_positives": 0,
|
| 547 |
+
"large_training_precision_at_0_5": 0.0,
|
| 548 |
+
"large_training_recall_at_0_5": 0.0,
|
| 549 |
+
"macro_f1": 0.4,
|
| 550 |
+
"rows": 55
|
| 551 |
+
}
|
| 552 |
+
],
|
| 553 |
+
"scenario_family": [
|
| 554 |
+
{
|
| 555 |
+
"accuracy": 1.0,
|
| 556 |
+
"group": "idle_or_low_activity",
|
| 557 |
+
"large_training_false_negatives": 0,
|
| 558 |
+
"large_training_false_positives": 0,
|
| 559 |
+
"large_training_precision_at_0_5": 0.0,
|
| 560 |
+
"large_training_recall_at_0_5": 0.0,
|
| 561 |
+
"macro_f1": 0.4,
|
| 562 |
+
"rows": 807
|
| 563 |
+
},
|
| 564 |
+
{
|
| 565 |
+
"accuracy": 1.0,
|
| 566 |
+
"group": "hpc_mpi_collective",
|
| 567 |
+
"large_training_false_negatives": 0,
|
| 568 |
+
"large_training_false_positives": 0,
|
| 569 |
+
"large_training_precision_at_0_5": 0.0,
|
| 570 |
+
"large_training_recall_at_0_5": 0.0,
|
| 571 |
+
"macro_f1": 0.2,
|
| 572 |
+
"rows": 171
|
| 573 |
+
},
|
| 574 |
+
{
|
| 575 |
+
"accuracy": 0.9872611464968153,
|
| 576 |
+
"group": "fragmented_training_linked",
|
| 577 |
+
"large_training_false_negatives": 2,
|
| 578 |
+
"large_training_false_positives": 0,
|
| 579 |
+
"large_training_precision_at_0_5": 1.0,
|
| 580 |
+
"large_training_recall_at_0_5": 0.9672131147540983,
|
| 581 |
+
"macro_f1": 0.3946048109965636,
|
| 582 |
+
"rows": 157
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"accuracy": 1.0,
|
| 586 |
+
"group": "training_without_semantic_logs",
|
| 587 |
+
"large_training_false_negatives": 0,
|
| 588 |
+
"large_training_false_positives": 0,
|
| 589 |
+
"large_training_precision_at_0_5": 1.0,
|
| 590 |
+
"large_training_recall_at_0_5": 1.0,
|
| 591 |
+
"macro_f1": 0.4,
|
| 592 |
+
"rows": 144
|
| 593 |
+
},
|
| 594 |
+
{
|
| 595 |
+
"accuracy": 1.0,
|
| 596 |
+
"group": "large_etl_or_data_movement",
|
| 597 |
+
"large_training_false_negatives": 0,
|
| 598 |
+
"large_training_false_positives": 0,
|
| 599 |
+
"large_training_precision_at_0_5": 0.0,
|
| 600 |
+
"large_training_recall_at_0_5": 0.0,
|
| 601 |
+
"macro_f1": 0.2,
|
| 602 |
+
"rows": 136
|
| 603 |
+
},
|
| 604 |
+
{
|
| 605 |
+
"accuracy": 1.0,
|
| 606 |
+
"group": "reserved_but_unused_capacity",
|
| 607 |
+
"large_training_false_negatives": 0,
|
| 608 |
+
"large_training_false_positives": 0,
|
| 609 |
+
"large_training_precision_at_0_5": 0.0,
|
| 610 |
+
"large_training_recall_at_0_5": 0.0,
|
| 611 |
+
"macro_f1": 0.2,
|
| 612 |
+
"rows": 122
|
| 613 |
+
},
|
| 614 |
+
{
|
| 615 |
+
"accuracy": 1.0,
|
| 616 |
+
"group": "model_parallel_inference",
|
| 617 |
+
"large_training_false_negatives": 0,
|
| 618 |
+
"large_training_false_positives": 0,
|
| 619 |
+
"large_training_precision_at_0_5": 0.0,
|
| 620 |
+
"large_training_recall_at_0_5": 0.0,
|
| 621 |
+
"macro_f1": 0.4,
|
| 622 |
+
"rows": 104
|
| 623 |
+
},
|
| 624 |
+
{
|
| 625 |
+
"accuracy": 1.0,
|
| 626 |
+
"group": "training_with_low_fabric_high_checkpoint",
|
| 627 |
+
"large_training_false_negatives": 0,
|
| 628 |
+
"large_training_false_positives": 0,
|
| 629 |
+
"large_training_precision_at_0_5": 1.0,
|
| 630 |
+
"large_training_recall_at_0_5": 1.0,
|
| 631 |
+
"macro_f1": 0.4,
|
| 632 |
+
"rows": 104
|
| 633 |
+
},
|
| 634 |
+
{
|
| 635 |
+
"accuracy": 1.0,
|
| 636 |
+
"group": "hardware_burn_in_or_thermal_soak",
|
| 637 |
+
"large_training_false_negatives": 0,
|
| 638 |
+
"large_training_false_positives": 0,
|
| 639 |
+
"large_training_precision_at_0_5": 0.0,
|
| 640 |
+
"large_training_recall_at_0_5": 0.0,
|
| 641 |
+
"macro_f1": 0.2,
|
| 642 |
+
"rows": 103
|
| 643 |
+
},
|
| 644 |
+
{
|
| 645 |
+
"accuracy": 0.6534653465346535,
|
| 646 |
+
"group": "normal_inference",
|
| 647 |
+
"large_training_false_negatives": 0,
|
| 648 |
+
"large_training_false_positives": 0,
|
| 649 |
+
"large_training_precision_at_0_5": 0.0,
|
| 650 |
+
"large_training_recall_at_0_5": 0.0,
|
| 651 |
+
"macro_f1": 0.277555239695051,
|
| 652 |
+
"rows": 101
|
| 653 |
+
},
|
| 654 |
+
{
|
| 655 |
+
"accuracy": 0.7956989247311828,
|
| 656 |
+
"group": "large_batch_inference",
|
| 657 |
+
"large_training_false_negatives": 0,
|
| 658 |
+
"large_training_false_positives": 0,
|
| 659 |
+
"large_training_precision_at_0_5": 0.0,
|
| 660 |
+
"large_training_recall_at_0_5": 0.0,
|
| 661 |
+
"macro_f1": 0.3176703552708212,
|
| 662 |
+
"rows": 93
|
| 663 |
+
},
|
| 664 |
+
{
|
| 665 |
+
"accuracy": 1.0,
|
| 666 |
+
"group": "training_with_delayed_logs",
|
| 667 |
+
"large_training_false_negatives": 0,
|
| 668 |
+
"large_training_false_positives": 0,
|
| 669 |
+
"large_training_precision_at_0_5": 1.0,
|
| 670 |
+
"large_training_recall_at_0_5": 1.0,
|
| 671 |
+
"macro_f1": 0.6,
|
| 672 |
+
"rows": 91
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"accuracy": 0.9550561797752809,
|
| 676 |
+
"group": "underclocked_energy_capped_training",
|
| 677 |
+
"large_training_false_negatives": 0,
|
| 678 |
+
"large_training_false_positives": 4,
|
| 679 |
+
"large_training_precision_at_0_5": 0.9384615384615385,
|
| 680 |
+
"large_training_recall_at_0_5": 1.0,
|
| 681 |
+
"macro_f1": 0.37826617826617825,
|
| 682 |
+
"rows": 89
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"accuracy": 1.0,
|
| 686 |
+
"group": "capacity_or_integrity_only_warning",
|
| 687 |
+
"large_training_false_negatives": 0,
|
| 688 |
+
"large_training_false_positives": 0,
|
| 689 |
+
"large_training_precision_at_0_5": 0.0,
|
| 690 |
+
"large_training_recall_at_0_5": 0.0,
|
| 691 |
+
"macro_f1": 0.2,
|
| 692 |
+
"rows": 87
|
| 693 |
+
},
|
| 694 |
+
{
|
| 695 |
+
"accuracy": 1.0,
|
| 696 |
+
"group": "cloud_training_redacted_runtime",
|
| 697 |
+
"large_training_false_negatives": 0,
|
| 698 |
+
"large_training_false_positives": 0,
|
| 699 |
+
"large_training_precision_at_0_5": 1.0,
|
| 700 |
+
"large_training_recall_at_0_5": 1.0,
|
| 701 |
+
"macro_f1": 0.4,
|
| 702 |
+
"rows": 86
|
| 703 |
+
},
|
| 704 |
+
{
|
| 705 |
+
"accuracy": 1.0,
|
| 706 |
+
"group": "pretraining_standard",
|
| 707 |
+
"large_training_false_negatives": 0,
|
| 708 |
+
"large_training_false_positives": 0,
|
| 709 |
+
"large_training_precision_at_0_5": 1.0,
|
| 710 |
+
"large_training_recall_at_0_5": 1.0,
|
| 711 |
+
"macro_f1": 0.2,
|
| 712 |
+
"rows": 86
|
| 713 |
+
},
|
| 714 |
+
{
|
| 715 |
+
"accuracy": 1.0,
|
| 716 |
+
"group": "multi_tenant_fragmented_nontraining",
|
| 717 |
+
"large_training_false_negatives": 0,
|
| 718 |
+
"large_training_false_positives": 0,
|
| 719 |
+
"large_training_precision_at_0_5": 0.0,
|
| 720 |
+
"large_training_recall_at_0_5": 0.0,
|
| 721 |
+
"macro_f1": 0.2,
|
| 722 |
+
"rows": 84
|
| 723 |
+
},
|
| 724 |
+
{
|
| 725 |
+
"accuracy": 1.0,
|
| 726 |
+
"group": "storage_rebuild_or_replication",
|
| 727 |
+
"large_training_false_negatives": 0,
|
| 728 |
+
"large_training_false_positives": 0,
|
| 729 |
+
"large_training_precision_at_0_5": 0.0,
|
| 730 |
+
"large_training_recall_at_0_5": 0.0,
|
| 731 |
+
"macro_f1": 0.4,
|
| 732 |
+
"rows": 82
|
| 733 |
+
},
|
| 734 |
+
{
|
| 735 |
+
"accuracy": 1.0,
|
| 736 |
+
"group": "nccl_extended_benchmark",
|
| 737 |
+
"large_training_false_negatives": 0,
|
| 738 |
+
"large_training_false_positives": 0,
|
| 739 |
+
"large_training_precision_at_0_5": 0.0,
|
| 740 |
+
"large_training_recall_at_0_5": 0.0,
|
| 741 |
+
"macro_f1": 0.2,
|
| 742 |
+
"rows": 69
|
| 743 |
+
},
|
| 744 |
+
{
|
| 745 |
+
"accuracy": 0.6296296296296297,
|
| 746 |
+
"group": "embedding_generation",
|
| 747 |
+
"large_training_false_negatives": 0,
|
| 748 |
+
"large_training_false_positives": 0,
|
| 749 |
+
"large_training_precision_at_0_5": 0.0,
|
| 750 |
+
"large_training_recall_at_0_5": 0.0,
|
| 751 |
+
"macro_f1": 0.15454545454545454,
|
| 752 |
+
"rows": 54
|
| 753 |
+
},
|
| 754 |
+
{
|
| 755 |
+
"accuracy": 1.0,
|
| 756 |
+
"group": "large_fine_tune_standard",
|
| 757 |
+
"large_training_false_negatives": 0,
|
| 758 |
+
"large_training_false_positives": 0,
|
| 759 |
+
"large_training_precision_at_0_5": 1.0,
|
| 760 |
+
"large_training_recall_at_0_5": 1.0,
|
| 761 |
+
"macro_f1": 0.4,
|
| 762 |
+
"rows": 42
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"accuracy": 1.0,
|
| 766 |
+
"group": "multi_stage_training_pipeline",
|
| 767 |
+
"large_training_false_negatives": 0,
|
| 768 |
+
"large_training_false_positives": 0,
|
| 769 |
+
"large_training_precision_at_0_5": 1.0,
|
| 770 |
+
"large_training_recall_at_0_5": 1.0,
|
| 771 |
+
"macro_f1": 0.4,
|
| 772 |
+
"rows": 24
|
| 773 |
+
},
|
| 774 |
+
{
|
| 775 |
+
"accuracy": 1.0,
|
| 776 |
+
"group": "counter_suppressed_candidate_window",
|
| 777 |
+
"large_training_false_negatives": 0,
|
| 778 |
+
"large_training_false_positives": 0,
|
| 779 |
+
"large_training_precision_at_0_5": 0.0,
|
| 780 |
+
"large_training_recall_at_0_5": 0.0,
|
| 781 |
+
"macro_f1": 0.2,
|
| 782 |
+
"rows": 22
|
| 783 |
+
},
|
| 784 |
+
{
|
| 785 |
+
"accuracy": 1.0,
|
| 786 |
+
"group": "distributed_database_or_graph_analytics",
|
| 787 |
+
"large_training_false_negatives": 0,
|
| 788 |
+
"large_training_false_positives": 0,
|
| 789 |
+
"large_training_precision_at_0_5": 0.0,
|
| 790 |
+
"large_training_recall_at_0_5": 0.0,
|
| 791 |
+
"macro_f1": 0.2,
|
| 792 |
+
"rows": 18
|
| 793 |
+
},
|
| 794 |
+
{
|
| 795 |
+
"accuracy": 1.0,
|
| 796 |
+
"group": "maintenance_with_collector_gaps",
|
| 797 |
+
"large_training_false_negatives": 0,
|
| 798 |
+
"large_training_false_positives": 0,
|
| 799 |
+
"large_training_precision_at_0_5": 0.0,
|
| 800 |
+
"large_training_recall_at_0_5": 0.0,
|
| 801 |
+
"macro_f1": 0.2,
|
| 802 |
+
"rows": 15
|
| 803 |
+
},
|
| 804 |
+
{
|
| 805 |
+
"accuracy": 1.0,
|
| 806 |
+
"group": "synthetic_data_generation_gpu_heavy",
|
| 807 |
+
"large_training_false_negatives": 0,
|
| 808 |
+
"large_training_false_positives": 0,
|
| 809 |
+
"large_training_precision_at_0_5": 0.0,
|
| 810 |
+
"large_training_recall_at_0_5": 0.0,
|
| 811 |
+
"macro_f1": 0.2,
|
| 812 |
+
"rows": 14
|
| 813 |
+
}
|
| 814 |
+
],
|
| 815 |
+
"site_id": [
|
| 816 |
+
{
|
| 817 |
+
"accuracy": 0.9966101694915255,
|
| 818 |
+
"group": "site_b",
|
| 819 |
+
"large_training_false_negatives": 2,
|
| 820 |
+
"large_training_false_positives": 0,
|
| 821 |
+
"large_training_precision_at_0_5": 1.0,
|
| 822 |
+
"large_training_recall_at_0_5": 0.9878787878787879,
|
| 823 |
+
"macro_f1": 0.9975238356540229,
|
| 824 |
+
"rows": 590
|
| 825 |
+
},
|
| 826 |
+
{
|
| 827 |
+
"accuracy": 1.0,
|
| 828 |
+
"group": "site_a",
|
| 829 |
+
"large_training_false_negatives": 0,
|
| 830 |
+
"large_training_false_positives": 0,
|
| 831 |
+
"large_training_precision_at_0_5": 1.0,
|
| 832 |
+
"large_training_recall_at_0_5": 1.0,
|
| 833 |
+
"macro_f1": 1.0,
|
| 834 |
+
"rows": 464
|
| 835 |
+
},
|
| 836 |
+
{
|
| 837 |
+
"accuracy": 1.0,
|
| 838 |
+
"group": "site_d",
|
| 839 |
+
"large_training_false_negatives": 0,
|
| 840 |
+
"large_training_false_positives": 0,
|
| 841 |
+
"large_training_precision_at_0_5": 1.0,
|
| 842 |
+
"large_training_recall_at_0_5": 1.0,
|
| 843 |
+
"macro_f1": 0.8,
|
| 844 |
+
"rows": 454
|
| 845 |
+
},
|
| 846 |
+
{
|
| 847 |
+
"accuracy": 0.9016786570743405,
|
| 848 |
+
"group": "site_g",
|
| 849 |
+
"large_training_false_negatives": 0,
|
| 850 |
+
"large_training_false_positives": 0,
|
| 851 |
+
"large_training_precision_at_0_5": 1.0,
|
| 852 |
+
"large_training_recall_at_0_5": 1.0,
|
| 853 |
+
"macro_f1": 0.9040527108006021,
|
| 854 |
+
"rows": 417
|
| 855 |
+
},
|
| 856 |
+
{
|
| 857 |
+
"accuracy": 0.948051948051948,
|
| 858 |
+
"group": "site_e",
|
| 859 |
+
"large_training_false_negatives": 0,
|
| 860 |
+
"large_training_false_positives": 0,
|
| 861 |
+
"large_training_precision_at_0_5": 1.0,
|
| 862 |
+
"large_training_recall_at_0_5": 1.0,
|
| 863 |
+
"macro_f1": 0.7622607921673342,
|
| 864 |
+
"rows": 385
|
| 865 |
+
},
|
| 866 |
+
{
|
| 867 |
+
"accuracy": 0.9872611464968153,
|
| 868 |
+
"group": "site_c",
|
| 869 |
+
"large_training_false_negatives": 0,
|
| 870 |
+
"large_training_false_positives": 4,
|
| 871 |
+
"large_training_precision_at_0_5": 0.8333333333333334,
|
| 872 |
+
"large_training_recall_at_0_5": 1.0,
|
| 873 |
+
"macro_f1": 0.776940133037694,
|
| 874 |
+
"rows": 314
|
| 875 |
+
},
|
| 876 |
+
{
|
| 877 |
+
"accuracy": 0.9537366548042705,
|
| 878 |
+
"group": "site_f",
|
| 879 |
+
"large_training_false_negatives": 0,
|
| 880 |
+
"large_training_false_positives": 0,
|
| 881 |
+
"large_training_precision_at_0_5": 1.0,
|
| 882 |
+
"large_training_recall_at_0_5": 1.0,
|
| 883 |
+
"macro_f1": 0.7762378716744914,
|
| 884 |
+
"rows": 281
|
| 885 |
+
}
|
| 886 |
+
],
|
| 887 |
+
"temporal_phase": [],
|
| 888 |
+
"window_length_seconds": [
|
| 889 |
+
{
|
| 890 |
+
"accuracy": 0.9710467706013363,
|
| 891 |
+
"group": "900",
|
| 892 |
+
"large_training_false_negatives": 1,
|
| 893 |
+
"large_training_false_positives": 1,
|
| 894 |
+
"large_training_precision_at_0_5": 0.9942528735632183,
|
| 895 |
+
"large_training_recall_at_0_5": 0.9942528735632183,
|
| 896 |
+
"macro_f1": 0.976753637450509,
|
| 897 |
+
"rows": 898
|
| 898 |
+
},
|
| 899 |
+
{
|
| 900 |
+
"accuracy": 0.9720044792833147,
|
| 901 |
+
"group": "3600",
|
| 902 |
+
"large_training_false_negatives": 1,
|
| 903 |
+
"large_training_false_positives": 1,
|
| 904 |
+
"large_training_precision_at_0_5": 0.9942196531791907,
|
| 905 |
+
"large_training_recall_at_0_5": 0.9942196531791907,
|
| 906 |
+
"macro_f1": 0.9774669561641736,
|
| 907 |
+
"rows": 893
|
| 908 |
+
},
|
| 909 |
+
{
|
| 910 |
+
"accuracy": 0.9731903485254692,
|
| 911 |
+
"group": "21600",
|
| 912 |
+
"large_training_false_negatives": 0,
|
| 913 |
+
"large_training_false_positives": 1,
|
| 914 |
+
"large_training_precision_at_0_5": 0.9942528735632183,
|
| 915 |
+
"large_training_recall_at_0_5": 1.0,
|
| 916 |
+
"macro_f1": 0.9793116710307792,
|
| 917 |
+
"rows": 746
|
| 918 |
+
},
|
| 919 |
+
{
|
| 920 |
+
"accuracy": 0.9755434782608695,
|
| 921 |
+
"group": "86400",
|
| 922 |
+
"large_training_false_negatives": 0,
|
| 923 |
+
"large_training_false_positives": 1,
|
| 924 |
+
"large_training_precision_at_0_5": 0.9895833333333334,
|
| 925 |
+
"large_training_recall_at_0_5": 1.0,
|
| 926 |
+
"macro_f1": 0.9804646860359316,
|
| 927 |
+
"rows": 368
|
| 928 |
+
}
|
| 929 |
+
]
|
| 930 |
+
}
|
| 931 |
+
}
|
data/model_runs/synthetic_v1_baseline/model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06c701b0d9c74cdfd0066fe3212013ad2393c3eca34ca625c78792631fa3f338
|
| 3 |
+
size 2178380
|
data/model_runs/synthetic_v1_baseline/predictions_all.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e2e66b8c5f743d13bb4a24b57656cf7f246cb423071efc1049a9f11019e8f549
|
| 3 |
+
size 12768446
|
data/model_runs/synthetic_v1_baseline/predictions_test.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_runs/synthetic_v1_baseline/preprocessing.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:edf78f71a10335c7703a183a76b97add8ee6ecc8b0ca1068fdcdedab18bcf2de
|
| 3 |
+
size 23460
|
data/model_runs/synthetic_v1_baseline/split_manifest.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/model_runs/synthetic_v1_baseline/stress_metrics.json
ADDED
|
@@ -0,0 +1,707 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"exclusion_metadata": {
|
| 3 |
+
"missing_requested_excluded_columns": [],
|
| 4 |
+
"notes": [
|
| 5 |
+
"Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
|
| 6 |
+
"scope_type and window_length_seconds are retained because they are valid deployment-time context.",
|
| 7 |
+
"Constant version metadata columns are excluded from the baseline."
|
| 8 |
+
],
|
| 9 |
+
"present_excluded_columns": [
|
| 10 |
+
"feature_row_id",
|
| 11 |
+
"dataset_id",
|
| 12 |
+
"seed",
|
| 13 |
+
"site_id",
|
| 14 |
+
"scope_id_hash",
|
| 15 |
+
"window_start",
|
| 16 |
+
"window_end",
|
| 17 |
+
"episode_id",
|
| 18 |
+
"label_0_to_4",
|
| 19 |
+
"label_confidence",
|
| 20 |
+
"label_reason",
|
| 21 |
+
"label_source",
|
| 22 |
+
"raw_input_manifest_hash",
|
| 23 |
+
"latent_workload_class",
|
| 24 |
+
"scenario_family",
|
| 25 |
+
"scenario_variant",
|
| 26 |
+
"evidence_recipe_id",
|
| 27 |
+
"counterfactual_group_id",
|
| 28 |
+
"synthetic_counterfactual_role",
|
| 29 |
+
"data_quality_regime",
|
| 30 |
+
"privacy_tier",
|
| 31 |
+
"collector_profile",
|
| 32 |
+
"topology_class",
|
| 33 |
+
"temporal_phase",
|
| 34 |
+
"synthetic_hard_case_tags",
|
| 35 |
+
"synthetic_evidence_profile",
|
| 36 |
+
"capacity_evidence_only",
|
| 37 |
+
"integrity_evidence_only",
|
| 38 |
+
"physical_evidence_only",
|
| 39 |
+
"feature_pipeline_version",
|
| 40 |
+
"policy_threshold_version",
|
| 41 |
+
"hardware_normalization_version"
|
| 42 |
+
],
|
| 43 |
+
"requested_excluded_columns": [
|
| 44 |
+
"feature_row_id",
|
| 45 |
+
"dataset_id",
|
| 46 |
+
"seed",
|
| 47 |
+
"site_id",
|
| 48 |
+
"scope_id_hash",
|
| 49 |
+
"window_start",
|
| 50 |
+
"window_end",
|
| 51 |
+
"episode_id",
|
| 52 |
+
"label_0_to_4",
|
| 53 |
+
"label_confidence",
|
| 54 |
+
"label_reason",
|
| 55 |
+
"label_source",
|
| 56 |
+
"raw_input_manifest_hash",
|
| 57 |
+
"latent_workload_class",
|
| 58 |
+
"scenario_family",
|
| 59 |
+
"scenario_variant",
|
| 60 |
+
"evidence_recipe_id",
|
| 61 |
+
"counterfactual_group_id",
|
| 62 |
+
"synthetic_counterfactual_role",
|
| 63 |
+
"data_quality_regime",
|
| 64 |
+
"privacy_tier",
|
| 65 |
+
"collector_profile",
|
| 66 |
+
"topology_class",
|
| 67 |
+
"temporal_phase",
|
| 68 |
+
"synthetic_hard_case_tags",
|
| 69 |
+
"synthetic_evidence_profile",
|
| 70 |
+
"capacity_evidence_only",
|
| 71 |
+
"integrity_evidence_only",
|
| 72 |
+
"physical_evidence_only",
|
| 73 |
+
"feature_pipeline_version",
|
| 74 |
+
"policy_threshold_version",
|
| 75 |
+
"hardware_normalization_version"
|
| 76 |
+
]
|
| 77 |
+
},
|
| 78 |
+
"features_path": "data/synthetic_v1/features/window_features_all.csv",
|
| 79 |
+
"max_iter": 90,
|
| 80 |
+
"model_feature_count": 152,
|
| 81 |
+
"scenario_column": "scenario_family",
|
| 82 |
+
"seed": 20260510,
|
| 83 |
+
"splits": {
|
| 84 |
+
"episode_grouped_random": {
|
| 85 |
+
"accuracy": 0.9776247848537005,
|
| 86 |
+
"false_negative_scenarios": {
|
| 87 |
+
"fragmented_training_linked": 2
|
| 88 |
+
},
|
| 89 |
+
"false_positive_scenarios": {
|
| 90 |
+
"underclocked_energy_capped_training": 4
|
| 91 |
+
},
|
| 92 |
+
"large_training": {
|
| 93 |
+
"f1": 0.9951298701298701,
|
| 94 |
+
"precision": 0.993517017828201,
|
| 95 |
+
"recall": 0.9967479674796748
|
| 96 |
+
},
|
| 97 |
+
"large_training_false_negative_count": 2,
|
| 98 |
+
"large_training_false_positive_count": 4,
|
| 99 |
+
"log_loss": 0.09684510600740838,
|
| 100 |
+
"macro_f1": 0.9820361510992616,
|
| 101 |
+
"middle_probability_bins": {
|
| 102 |
+
"0.1_to_0.3": 18,
|
| 103 |
+
"0.3_to_0.7": 2,
|
| 104 |
+
"0.7_to_0.9": 0
|
| 105 |
+
},
|
| 106 |
+
"test_episodes": 150,
|
| 107 |
+
"test_label_distribution": {
|
| 108 |
+
"0": 762,
|
| 109 |
+
"1": 690,
|
| 110 |
+
"2": 838,
|
| 111 |
+
"3": 463,
|
| 112 |
+
"4": 152
|
| 113 |
+
},
|
| 114 |
+
"test_rows": 2905,
|
| 115 |
+
"train_episodes": 610,
|
| 116 |
+
"train_rows": 11807
|
| 117 |
+
},
|
| 118 |
+
"scenario_family_holdout": {
|
| 119 |
+
"fragmented_training_linked": {
|
| 120 |
+
"accuracy": 0.8805774278215223,
|
| 121 |
+
"false_negative_scenarios": {
|
| 122 |
+
"fragmented_training_linked": 91
|
| 123 |
+
},
|
| 124 |
+
"false_positive_scenarios": {},
|
| 125 |
+
"large_training": {
|
| 126 |
+
"f1": 0.8465430016863407,
|
| 127 |
+
"precision": 1.0,
|
| 128 |
+
"recall": 0.7339181286549707
|
| 129 |
+
},
|
| 130 |
+
"large_training_false_negative_count": 91,
|
| 131 |
+
"large_training_false_positive_count": 0,
|
| 132 |
+
"log_loss": 0.23491922435876197,
|
| 133 |
+
"macro_f1": 0.3501482128238128,
|
| 134 |
+
"middle_probability_bins": {
|
| 135 |
+
"0.1_to_0.3": 6,
|
| 136 |
+
"0.3_to_0.7": 81,
|
| 137 |
+
"0.7_to_0.9": 0
|
| 138 |
+
},
|
| 139 |
+
"test_episodes": 35,
|
| 140 |
+
"test_label_distribution": {
|
| 141 |
+
"2": 420,
|
| 142 |
+
"3": 342
|
| 143 |
+
},
|
| 144 |
+
"test_rows": 762,
|
| 145 |
+
"train_episodes": 725,
|
| 146 |
+
"train_rows": 13950
|
| 147 |
+
},
|
| 148 |
+
"hpc_mpi_collective": {
|
| 149 |
+
"accuracy": 0.9530516431924883,
|
| 150 |
+
"false_negative_scenarios": {},
|
| 151 |
+
"false_positive_scenarios": {},
|
| 152 |
+
"large_training": {
|
| 153 |
+
"f1": 0.0,
|
| 154 |
+
"precision": 0.0,
|
| 155 |
+
"recall": 0.0
|
| 156 |
+
},
|
| 157 |
+
"large_training_false_negative_count": 0,
|
| 158 |
+
"large_training_false_positive_count": 0,
|
| 159 |
+
"log_loss": 0.24695636309738603,
|
| 160 |
+
"macro_f1": 0.19744680851063828,
|
| 161 |
+
"middle_probability_bins": {
|
| 162 |
+
"0.1_to_0.3": 58,
|
| 163 |
+
"0.3_to_0.7": 0,
|
| 164 |
+
"0.7_to_0.9": 0
|
| 165 |
+
},
|
| 166 |
+
"test_episodes": 42,
|
| 167 |
+
"test_label_distribution": {
|
| 168 |
+
"0": 34,
|
| 169 |
+
"2": 818
|
| 170 |
+
},
|
| 171 |
+
"test_rows": 852,
|
| 172 |
+
"train_episodes": 718,
|
| 173 |
+
"train_rows": 13860
|
| 174 |
+
},
|
| 175 |
+
"model_parallel_inference": {
|
| 176 |
+
"accuracy": 0.8814655172413793,
|
| 177 |
+
"false_negative_scenarios": {},
|
| 178 |
+
"false_positive_scenarios": {},
|
| 179 |
+
"large_training": {
|
| 180 |
+
"f1": 0.0,
|
| 181 |
+
"precision": 0.0,
|
| 182 |
+
"recall": 0.0
|
| 183 |
+
},
|
| 184 |
+
"large_training_false_negative_count": 0,
|
| 185 |
+
"large_training_false_positive_count": 0,
|
| 186 |
+
"log_loss": 0.32601787797092746,
|
| 187 |
+
"macro_f1": 0.32858292230734254,
|
| 188 |
+
"middle_probability_bins": {
|
| 189 |
+
"0.1_to_0.3": 0,
|
| 190 |
+
"0.3_to_0.7": 0,
|
| 191 |
+
"0.7_to_0.9": 0
|
| 192 |
+
},
|
| 193 |
+
"test_episodes": 23,
|
| 194 |
+
"test_label_distribution": {
|
| 195 |
+
"1": 125,
|
| 196 |
+
"2": 339
|
| 197 |
+
},
|
| 198 |
+
"test_rows": 464,
|
| 199 |
+
"train_episodes": 737,
|
| 200 |
+
"train_rows": 14248
|
| 201 |
+
},
|
| 202 |
+
"multi_tenant_fragmented_nontraining": {
|
| 203 |
+
"accuracy": 1.0,
|
| 204 |
+
"false_negative_scenarios": {},
|
| 205 |
+
"false_positive_scenarios": {},
|
| 206 |
+
"large_training": {
|
| 207 |
+
"f1": 0.0,
|
| 208 |
+
"precision": 0.0,
|
| 209 |
+
"recall": 0.0
|
| 210 |
+
},
|
| 211 |
+
"large_training_false_negative_count": 0,
|
| 212 |
+
"large_training_false_positive_count": 0,
|
| 213 |
+
"log_loss": 0.0001384917381362078,
|
| 214 |
+
"macro_f1": 0.2,
|
| 215 |
+
"middle_probability_bins": {
|
| 216 |
+
"0.1_to_0.3": 0,
|
| 217 |
+
"0.3_to_0.7": 0,
|
| 218 |
+
"0.7_to_0.9": 0
|
| 219 |
+
},
|
| 220 |
+
"test_episodes": 22,
|
| 221 |
+
"test_label_distribution": {
|
| 222 |
+
"2": 461
|
| 223 |
+
},
|
| 224 |
+
"test_rows": 461,
|
| 225 |
+
"train_episodes": 738,
|
| 226 |
+
"train_rows": 14251
|
| 227 |
+
},
|
| 228 |
+
"underclocked_energy_capped_training": {
|
| 229 |
+
"accuracy": 0.979539641943734,
|
| 230 |
+
"false_negative_scenarios": {},
|
| 231 |
+
"false_positive_scenarios": {
|
| 232 |
+
"underclocked_energy_capped_training": 8
|
| 233 |
+
},
|
| 234 |
+
"large_training": {
|
| 235 |
+
"f1": 0.9873015873015873,
|
| 236 |
+
"precision": 0.9749216300940439,
|
| 237 |
+
"recall": 1.0
|
| 238 |
+
},
|
| 239 |
+
"large_training_false_negative_count": 0,
|
| 240 |
+
"large_training_false_positive_count": 8,
|
| 241 |
+
"log_loss": 0.1272874809495267,
|
| 242 |
+
"macro_f1": 0.38693400167084374,
|
| 243 |
+
"middle_probability_bins": {
|
| 244 |
+
"0.1_to_0.3": 0,
|
| 245 |
+
"0.3_to_0.7": 0,
|
| 246 |
+
"0.7_to_0.9": 0
|
| 247 |
+
},
|
| 248 |
+
"test_episodes": 18,
|
| 249 |
+
"test_label_distribution": {
|
| 250 |
+
"2": 80,
|
| 251 |
+
"3": 311
|
| 252 |
+
},
|
| 253 |
+
"test_rows": 391,
|
| 254 |
+
"train_episodes": 742,
|
| 255 |
+
"train_rows": 14321
|
| 256 |
+
}
|
| 257 |
+
},
|
| 258 |
+
"site_holdout": {
|
| 259 |
+
"site_a": {
|
| 260 |
+
"accuracy": 0.9651113913408995,
|
| 261 |
+
"false_negative_scenarios": {
|
| 262 |
+
"fragmented_training_linked": 5
|
| 263 |
+
},
|
| 264 |
+
"false_positive_scenarios": {},
|
| 265 |
+
"large_training": {
|
| 266 |
+
"f1": 0.9958643507030603,
|
| 267 |
+
"precision": 1.0,
|
| 268 |
+
"recall": 0.9917627677100495
|
| 269 |
+
},
|
| 270 |
+
"large_training_false_negative_count": 5,
|
| 271 |
+
"large_training_false_positive_count": 0,
|
| 272 |
+
"log_loss": 0.12471327669067636,
|
| 273 |
+
"macro_f1": 0.969965539074414,
|
| 274 |
+
"middle_probability_bins": {
|
| 275 |
+
"0.1_to_0.3": 3,
|
| 276 |
+
"0.3_to_0.7": 6,
|
| 277 |
+
"0.7_to_0.9": 0
|
| 278 |
+
},
|
| 279 |
+
"test_episodes": 119,
|
| 280 |
+
"test_label_distribution": {
|
| 281 |
+
"0": 523,
|
| 282 |
+
"1": 498,
|
| 283 |
+
"2": 751,
|
| 284 |
+
"3": 428,
|
| 285 |
+
"4": 179
|
| 286 |
+
},
|
| 287 |
+
"test_rows": 2379,
|
| 288 |
+
"train_episodes": 641,
|
| 289 |
+
"train_rows": 12333
|
| 290 |
+
},
|
| 291 |
+
"site_b": {
|
| 292 |
+
"accuracy": 0.9817596566523605,
|
| 293 |
+
"false_negative_scenarios": {},
|
| 294 |
+
"false_positive_scenarios": {},
|
| 295 |
+
"large_training": {
|
| 296 |
+
"f1": 1.0,
|
| 297 |
+
"precision": 1.0,
|
| 298 |
+
"recall": 1.0
|
| 299 |
+
},
|
| 300 |
+
"large_training_false_negative_count": 0,
|
| 301 |
+
"large_training_false_positive_count": 0,
|
| 302 |
+
"log_loss": 0.12083701002956747,
|
| 303 |
+
"macro_f1": 0.9823840732636331,
|
| 304 |
+
"middle_probability_bins": {
|
| 305 |
+
"0.1_to_0.3": 59,
|
| 306 |
+
"0.3_to_0.7": 0,
|
| 307 |
+
"0.7_to_0.9": 0
|
| 308 |
+
},
|
| 309 |
+
"test_episodes": 143,
|
| 310 |
+
"test_label_distribution": {
|
| 311 |
+
"0": 766,
|
| 312 |
+
"1": 442,
|
| 313 |
+
"2": 923,
|
| 314 |
+
"3": 472,
|
| 315 |
+
"4": 193
|
| 316 |
+
},
|
| 317 |
+
"test_rows": 2796,
|
| 318 |
+
"train_episodes": 617,
|
| 319 |
+
"train_rows": 11916
|
| 320 |
+
},
|
| 321 |
+
"site_c": {
|
| 322 |
+
"accuracy": 0.9793233082706767,
|
| 323 |
+
"false_negative_scenarios": {},
|
| 324 |
+
"false_positive_scenarios": {
|
| 325 |
+
"underclocked_energy_capped_training": 4
|
| 326 |
+
},
|
| 327 |
+
"large_training": {
|
| 328 |
+
"f1": 0.993485342019544,
|
| 329 |
+
"precision": 0.9870550161812298,
|
| 330 |
+
"recall": 1.0
|
| 331 |
+
},
|
| 332 |
+
"large_training_false_negative_count": 0,
|
| 333 |
+
"large_training_false_positive_count": 4,
|
| 334 |
+
"log_loss": 0.12938808464320045,
|
| 335 |
+
"macro_f1": 0.9812786466966241,
|
| 336 |
+
"middle_probability_bins": {
|
| 337 |
+
"0.1_to_0.3": 0,
|
| 338 |
+
"0.3_to_0.7": 0,
|
| 339 |
+
"0.7_to_0.9": 0
|
| 340 |
+
},
|
| 341 |
+
"test_episodes": 83,
|
| 342 |
+
"test_label_distribution": {
|
| 343 |
+
"0": 544,
|
| 344 |
+
"1": 314,
|
| 345 |
+
"2": 433,
|
| 346 |
+
"3": 216,
|
| 347 |
+
"4": 89
|
| 348 |
+
},
|
| 349 |
+
"test_rows": 1596,
|
| 350 |
+
"train_episodes": 677,
|
| 351 |
+
"train_rows": 13116
|
| 352 |
+
},
|
| 353 |
+
"site_d": {
|
| 354 |
+
"accuracy": 0.9671179883945842,
|
| 355 |
+
"false_negative_scenarios": {},
|
| 356 |
+
"false_positive_scenarios": {},
|
| 357 |
+
"large_training": {
|
| 358 |
+
"f1": 1.0,
|
| 359 |
+
"precision": 1.0,
|
| 360 |
+
"recall": 1.0
|
| 361 |
+
},
|
| 362 |
+
"large_training_false_negative_count": 0,
|
| 363 |
+
"large_training_false_positive_count": 0,
|
| 364 |
+
"log_loss": 0.1630732533805434,
|
| 365 |
+
"macro_f1": 0.9761029329988823,
|
| 366 |
+
"middle_probability_bins": {
|
| 367 |
+
"0.1_to_0.3": 20,
|
| 368 |
+
"0.3_to_0.7": 0,
|
| 369 |
+
"0.7_to_0.9": 0
|
| 370 |
+
},
|
| 371 |
+
"test_episodes": 134,
|
| 372 |
+
"test_label_distribution": {
|
| 373 |
+
"0": 657,
|
| 374 |
+
"1": 691,
|
| 375 |
+
"2": 751,
|
| 376 |
+
"3": 415,
|
| 377 |
+
"4": 71
|
| 378 |
+
},
|
| 379 |
+
"test_rows": 2585,
|
| 380 |
+
"train_episodes": 626,
|
| 381 |
+
"train_rows": 12127
|
| 382 |
+
},
|
| 383 |
+
"site_e": {
|
| 384 |
+
"accuracy": 0.9577039274924471,
|
| 385 |
+
"false_negative_scenarios": {
|
| 386 |
+
"training_without_semantic_logs": 4
|
| 387 |
+
},
|
| 388 |
+
"false_positive_scenarios": {},
|
| 389 |
+
"large_training": {
|
| 390 |
+
"f1": 0.9952718676122931,
|
| 391 |
+
"precision": 1.0,
|
| 392 |
+
"recall": 0.9905882352941177
|
| 393 |
+
},
|
| 394 |
+
"large_training_false_negative_count": 4,
|
| 395 |
+
"large_training_false_positive_count": 0,
|
| 396 |
+
"log_loss": 0.10221061129077724,
|
| 397 |
+
"macro_f1": 0.9672000210392337,
|
| 398 |
+
"middle_probability_bins": {
|
| 399 |
+
"0.1_to_0.3": 0,
|
| 400 |
+
"0.3_to_0.7": 0,
|
| 401 |
+
"0.7_to_0.9": 0
|
| 402 |
+
},
|
| 403 |
+
"test_episodes": 105,
|
| 404 |
+
"test_label_distribution": {
|
| 405 |
+
"0": 501,
|
| 406 |
+
"1": 505,
|
| 407 |
+
"2": 555,
|
| 408 |
+
"3": 323,
|
| 409 |
+
"4": 102
|
| 410 |
+
},
|
| 411 |
+
"test_rows": 1986,
|
| 412 |
+
"train_episodes": 655,
|
| 413 |
+
"train_rows": 12726
|
| 414 |
+
},
|
| 415 |
+
"site_f": {
|
| 416 |
+
"accuracy": 0.974571586511885,
|
| 417 |
+
"false_negative_scenarios": {
|
| 418 |
+
"fragmented_training_linked": 4
|
| 419 |
+
},
|
| 420 |
+
"false_positive_scenarios": {},
|
| 421 |
+
"large_training": {
|
| 422 |
+
"f1": 0.9951338199513382,
|
| 423 |
+
"precision": 1.0,
|
| 424 |
+
"recall": 0.9903147699757869
|
| 425 |
+
},
|
| 426 |
+
"large_training_false_negative_count": 4,
|
| 427 |
+
"large_training_false_positive_count": 0,
|
| 428 |
+
"log_loss": 0.12939604463893833,
|
| 429 |
+
"macro_f1": 0.979930256900869,
|
| 430 |
+
"middle_probability_bins": {
|
| 431 |
+
"0.1_to_0.3": 2,
|
| 432 |
+
"0.3_to_0.7": 4,
|
| 433 |
+
"0.7_to_0.9": 0
|
| 434 |
+
},
|
| 435 |
+
"test_episodes": 94,
|
| 436 |
+
"test_label_distribution": {
|
| 437 |
+
"0": 512,
|
| 438 |
+
"1": 449,
|
| 439 |
+
"2": 435,
|
| 440 |
+
"3": 330,
|
| 441 |
+
"4": 83
|
| 442 |
+
},
|
| 443 |
+
"test_rows": 1809,
|
| 444 |
+
"train_episodes": 666,
|
| 445 |
+
"train_rows": 12903
|
| 446 |
+
},
|
| 447 |
+
"site_g": {
|
| 448 |
+
"accuracy": 0.9647661755285074,
|
| 449 |
+
"false_negative_scenarios": {
|
| 450 |
+
"training_without_semantic_logs": 3
|
| 451 |
+
},
|
| 452 |
+
"false_positive_scenarios": {},
|
| 453 |
+
"large_training": {
|
| 454 |
+
"f1": 0.9963636363636363,
|
| 455 |
+
"precision": 1.0,
|
| 456 |
+
"recall": 0.9927536231884058
|
| 457 |
+
},
|
| 458 |
+
"large_training_false_negative_count": 3,
|
| 459 |
+
"large_training_false_positive_count": 0,
|
| 460 |
+
"log_loss": 0.05853045642772131,
|
| 461 |
+
"macro_f1": 0.9717372783794571,
|
| 462 |
+
"middle_probability_bins": {
|
| 463 |
+
"0.1_to_0.3": 0,
|
| 464 |
+
"0.3_to_0.7": 7,
|
| 465 |
+
"0.7_to_0.9": 5
|
| 466 |
+
},
|
| 467 |
+
"test_episodes": 82,
|
| 468 |
+
"test_label_distribution": {
|
| 469 |
+
"0": 358,
|
| 470 |
+
"1": 434,
|
| 471 |
+
"2": 355,
|
| 472 |
+
"3": 305,
|
| 473 |
+
"4": 109
|
| 474 |
+
},
|
| 475 |
+
"test_rows": 1561,
|
| 476 |
+
"train_episodes": 678,
|
| 477 |
+
"train_rows": 13151
|
| 478 |
+
}
|
| 479 |
+
},
|
| 480 |
+
"source_ablations": {
|
| 481 |
+
"source_ablation_drop_fabric": {
|
| 482 |
+
"accuracy": 0.9865748709122203,
|
| 483 |
+
"dropped_prefixes": [
|
| 484 |
+
"o6_",
|
| 485 |
+
"o7_",
|
| 486 |
+
"fabric_telemetry_trust_level"
|
| 487 |
+
],
|
| 488 |
+
"false_negative_scenarios": {},
|
| 489 |
+
"false_positive_scenarios": {
|
| 490 |
+
"underclocked_energy_capped_training": 4
|
| 491 |
+
},
|
| 492 |
+
"feature_count": 132,
|
| 493 |
+
"large_training": {
|
| 494 |
+
"f1": 0.9967585089141004,
|
| 495 |
+
"precision": 0.9935379644588045,
|
| 496 |
+
"recall": 1.0
|
| 497 |
+
},
|
| 498 |
+
"large_training_false_negative_count": 0,
|
| 499 |
+
"large_training_false_positive_count": 4,
|
| 500 |
+
"log_loss": 0.08731589431092471,
|
| 501 |
+
"macro_f1": 0.9891740593379061,
|
| 502 |
+
"middle_probability_bins": {
|
| 503 |
+
"0.1_to_0.3": 0,
|
| 504 |
+
"0.3_to_0.7": 0,
|
| 505 |
+
"0.7_to_0.9": 0
|
| 506 |
+
},
|
| 507 |
+
"test_episodes": 150,
|
| 508 |
+
"test_label_distribution": {
|
| 509 |
+
"0": 762,
|
| 510 |
+
"1": 690,
|
| 511 |
+
"2": 838,
|
| 512 |
+
"3": 463,
|
| 513 |
+
"4": 152
|
| 514 |
+
},
|
| 515 |
+
"test_rows": 2905,
|
| 516 |
+
"train_episodes": 610,
|
| 517 |
+
"train_rows": 11807
|
| 518 |
+
},
|
| 519 |
+
"source_ablation_drop_gpu_telemetry": {
|
| 520 |
+
"accuracy": 0.9645438898450946,
|
| 521 |
+
"dropped_prefixes": [
|
| 522 |
+
"o4_",
|
| 523 |
+
"o5_",
|
| 524 |
+
"gpu_telemetry_trust_level"
|
| 525 |
+
],
|
| 526 |
+
"false_negative_scenarios": {
|
| 527 |
+
"fragmented_training_linked": 2
|
| 528 |
+
},
|
| 529 |
+
"false_positive_scenarios": {
|
| 530 |
+
"underclocked_energy_capped_training": 4
|
| 531 |
+
},
|
| 532 |
+
"feature_count": 131,
|
| 533 |
+
"large_training": {
|
| 534 |
+
"f1": 0.9951298701298701,
|
| 535 |
+
"precision": 0.993517017828201,
|
| 536 |
+
"recall": 0.9967479674796748
|
| 537 |
+
},
|
| 538 |
+
"large_training_false_negative_count": 2,
|
| 539 |
+
"large_training_false_positive_count": 4,
|
| 540 |
+
"log_loss": 0.09299517347739551,
|
| 541 |
+
"macro_f1": 0.9719602384218333,
|
| 542 |
+
"middle_probability_bins": {
|
| 543 |
+
"0.1_to_0.3": 0,
|
| 544 |
+
"0.3_to_0.7": 0,
|
| 545 |
+
"0.7_to_0.9": 0
|
| 546 |
+
},
|
| 547 |
+
"test_episodes": 150,
|
| 548 |
+
"test_label_distribution": {
|
| 549 |
+
"0": 762,
|
| 550 |
+
"1": 690,
|
| 551 |
+
"2": 838,
|
| 552 |
+
"3": 463,
|
| 553 |
+
"4": 152
|
| 554 |
+
},
|
| 555 |
+
"test_rows": 2905,
|
| 556 |
+
"train_episodes": 610,
|
| 557 |
+
"train_rows": 11807
|
| 558 |
+
},
|
| 559 |
+
"source_ablation_drop_power": {
|
| 560 |
+
"accuracy": 0.9648881239242685,
|
| 561 |
+
"dropped_prefixes": [
|
| 562 |
+
"o8_",
|
| 563 |
+
"o9_",
|
| 564 |
+
"power_meter_trust_level"
|
| 565 |
+
],
|
| 566 |
+
"false_negative_scenarios": {
|
| 567 |
+
"training_with_delayed_logs": 4
|
| 568 |
+
},
|
| 569 |
+
"false_positive_scenarios": {
|
| 570 |
+
"underclocked_energy_capped_training": 4
|
| 571 |
+
},
|
| 572 |
+
"feature_count": 133,
|
| 573 |
+
"large_training": {
|
| 574 |
+
"f1": 0.9934959349593496,
|
| 575 |
+
"precision": 0.9934959349593496,
|
| 576 |
+
"recall": 0.9934959349593496
|
| 577 |
+
},
|
| 578 |
+
"large_training_false_negative_count": 4,
|
| 579 |
+
"large_training_false_positive_count": 4,
|
| 580 |
+
"log_loss": 0.14389758581141518,
|
| 581 |
+
"macro_f1": 0.9719275856055797,
|
| 582 |
+
"middle_probability_bins": {
|
| 583 |
+
"0.1_to_0.3": 4,
|
| 584 |
+
"0.3_to_0.7": 0,
|
| 585 |
+
"0.7_to_0.9": 0
|
| 586 |
+
},
|
| 587 |
+
"test_episodes": 150,
|
| 588 |
+
"test_label_distribution": {
|
| 589 |
+
"0": 762,
|
| 590 |
+
"1": 690,
|
| 591 |
+
"2": 838,
|
| 592 |
+
"3": 463,
|
| 593 |
+
"4": 152
|
| 594 |
+
},
|
| 595 |
+
"test_rows": 2905,
|
| 596 |
+
"train_episodes": 610,
|
| 597 |
+
"train_rows": 11807
|
| 598 |
+
},
|
| 599 |
+
"source_ablation_drop_runtime_and_ml_logs": {
|
| 600 |
+
"accuracy": 0.9380378657487092,
|
| 601 |
+
"dropped_prefixes": [
|
| 602 |
+
"o10_",
|
| 603 |
+
"o12_"
|
| 604 |
+
],
|
| 605 |
+
"false_negative_scenarios": {},
|
| 606 |
+
"false_positive_scenarios": {
|
| 607 |
+
"training_with_delayed_logs": 4,
|
| 608 |
+
"underclocked_energy_capped_training": 4
|
| 609 |
+
},
|
| 610 |
+
"feature_count": 132,
|
| 611 |
+
"large_training": {
|
| 612 |
+
"f1": 0.9935379644588045,
|
| 613 |
+
"precision": 0.9871589085072231,
|
| 614 |
+
"recall": 1.0
|
| 615 |
+
},
|
| 616 |
+
"large_training_false_negative_count": 0,
|
| 617 |
+
"large_training_false_positive_count": 8,
|
| 618 |
+
"log_loss": 0.35327183914707877,
|
| 619 |
+
"macro_f1": 0.8925854563387956,
|
| 620 |
+
"middle_probability_bins": {
|
| 621 |
+
"0.1_to_0.3": 4,
|
| 622 |
+
"0.3_to_0.7": 4,
|
| 623 |
+
"0.7_to_0.9": 44
|
| 624 |
+
},
|
| 625 |
+
"test_episodes": 150,
|
| 626 |
+
"test_label_distribution": {
|
| 627 |
+
"0": 762,
|
| 628 |
+
"1": 690,
|
| 629 |
+
"2": 838,
|
| 630 |
+
"3": 463,
|
| 631 |
+
"4": 152
|
| 632 |
+
},
|
| 633 |
+
"test_rows": 2905,
|
| 634 |
+
"train_episodes": 610,
|
| 635 |
+
"train_rows": 11807
|
| 636 |
+
},
|
| 637 |
+
"source_ablation_drop_storage": {
|
| 638 |
+
"accuracy": 0.9655765920826161,
|
| 639 |
+
"dropped_prefixes": [
|
| 640 |
+
"o11_"
|
| 641 |
+
],
|
| 642 |
+
"false_negative_scenarios": {
|
| 643 |
+
"fragmented_training_linked": 21
|
| 644 |
+
},
|
| 645 |
+
"false_positive_scenarios": {
|
| 646 |
+
"fragmented_training_linked": 7
|
| 647 |
+
},
|
| 648 |
+
"feature_count": 141,
|
| 649 |
+
"large_training": {
|
| 650 |
+
"f1": 0.9769736842105263,
|
| 651 |
+
"precision": 0.9883527454242929,
|
| 652 |
+
"recall": 0.9658536585365853
|
| 653 |
+
},
|
| 654 |
+
"large_training_false_negative_count": 21,
|
| 655 |
+
"large_training_false_positive_count": 7,
|
| 656 |
+
"log_loss": 0.12576959836354257,
|
| 657 |
+
"macro_f1": 0.9712103257601914,
|
| 658 |
+
"middle_probability_bins": {
|
| 659 |
+
"0.1_to_0.3": 13,
|
| 660 |
+
"0.3_to_0.7": 15,
|
| 661 |
+
"0.7_to_0.9": 8
|
| 662 |
+
},
|
| 663 |
+
"test_episodes": 150,
|
| 664 |
+
"test_label_distribution": {
|
| 665 |
+
"0": 762,
|
| 666 |
+
"1": 690,
|
| 667 |
+
"2": 838,
|
| 668 |
+
"3": 463,
|
| 669 |
+
"4": 152
|
| 670 |
+
},
|
| 671 |
+
"test_rows": 2905,
|
| 672 |
+
"train_episodes": 610,
|
| 673 |
+
"train_rows": 11807
|
| 674 |
+
}
|
| 675 |
+
},
|
| 676 |
+
"time_holdout": {
|
| 677 |
+
"accuracy": 0.980971797485559,
|
| 678 |
+
"false_negative_scenarios": {},
|
| 679 |
+
"false_positive_scenarios": {},
|
| 680 |
+
"large_training": {
|
| 681 |
+
"f1": 1.0,
|
| 682 |
+
"precision": 1.0,
|
| 683 |
+
"recall": 1.0
|
| 684 |
+
},
|
| 685 |
+
"large_training_false_negative_count": 0,
|
| 686 |
+
"large_training_false_positive_count": 0,
|
| 687 |
+
"log_loss": 0.12262942519580222,
|
| 688 |
+
"macro_f1": 0.9862673370214863,
|
| 689 |
+
"middle_probability_bins": {
|
| 690 |
+
"0.1_to_0.3": 4,
|
| 691 |
+
"0.3_to_0.7": 0,
|
| 692 |
+
"0.7_to_0.9": 0
|
| 693 |
+
},
|
| 694 |
+
"test_episodes": 179,
|
| 695 |
+
"test_label_distribution": {
|
| 696 |
+
"0": 715,
|
| 697 |
+
"1": 857,
|
| 698 |
+
"2": 776,
|
| 699 |
+
"3": 486,
|
| 700 |
+
"4": 109
|
| 701 |
+
},
|
| 702 |
+
"test_rows": 2943,
|
| 703 |
+
"train_episodes": 622,
|
| 704 |
+
"train_rows": 11769
|
| 705 |
+
}
|
| 706 |
+
}
|
| 707 |
+
}
|
data/model_runs/synthetic_v1_baseline/stress_summary.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Synthetic v1 Stress Summary
|
| 2 |
+
|
| 3 |
+
Feature table: `data/synthetic_v1/features/window_features_all.csv`
|
| 4 |
+
Seed: `20260510`
|
| 5 |
+
Lightweight stress model max_iter: `90`
|
| 6 |
+
|
| 7 |
+
## Core Splits
|
| 8 |
+
|
| 9 |
+
- episode_grouped_random: rows=2905, macro F1=0.982, large precision=0.994, large recall=0.997, FP=4, FN=2, middle p bins={'0.1_to_0.3': 18, '0.3_to_0.7': 2, '0.7_to_0.9': 0}
|
| 10 |
+
- time_holdout: rows=2943, macro F1=0.986, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 11 |
+
|
| 12 |
+
## Site Holdout
|
| 13 |
+
|
| 14 |
+
- site_a: rows=2379, macro F1=0.970, large precision=1.000, large recall=0.992, FP=0, FN=5, middle p bins={'0.1_to_0.3': 3, '0.3_to_0.7': 6, '0.7_to_0.9': 0}
|
| 15 |
+
- site_b: rows=2796, macro F1=0.982, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 59, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 16 |
+
- site_c: rows=1596, macro F1=0.981, large precision=0.987, large recall=1.000, FP=4, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 17 |
+
- site_d: rows=2585, macro F1=0.976, large precision=1.000, large recall=1.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 20, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 18 |
+
- site_e: rows=1986, macro F1=0.967, large precision=1.000, large recall=0.991, FP=0, FN=4, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 19 |
+
- site_f: rows=1809, macro F1=0.980, large precision=1.000, large recall=0.990, FP=0, FN=4, middle p bins={'0.1_to_0.3': 2, '0.3_to_0.7': 4, '0.7_to_0.9': 0}
|
| 20 |
+
- site_g: rows=1561, macro F1=0.972, large precision=1.000, large recall=0.993, FP=0, FN=3, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 7, '0.7_to_0.9': 5}
|
| 21 |
+
|
| 22 |
+
## Scenario Family Holdout
|
| 23 |
+
|
| 24 |
+
- underclocked_energy_capped_training: rows=391, macro F1=0.387, large precision=0.975, large recall=1.000, FP=8, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 25 |
+
false positives: {'underclocked_energy_capped_training': 8}
|
| 26 |
+
- fragmented_training_linked: rows=762, macro F1=0.350, large precision=1.000, large recall=0.734, FP=0, FN=91, middle p bins={'0.1_to_0.3': 6, '0.3_to_0.7': 81, '0.7_to_0.9': 0}
|
| 27 |
+
false negatives: {'fragmented_training_linked': 91}
|
| 28 |
+
- multi_tenant_fragmented_nontraining: rows=461, macro F1=0.200, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 29 |
+
- model_parallel_inference: rows=464, macro F1=0.329, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 30 |
+
- hpc_mpi_collective: rows=852, macro F1=0.197, large precision=0.000, large recall=0.000, FP=0, FN=0, middle p bins={'0.1_to_0.3': 58, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 31 |
+
|
| 32 |
+
## Source Ablations
|
| 33 |
+
|
| 34 |
+
- source_ablation_drop_fabric: rows=2905, macro F1=0.989, large precision=0.994, large recall=1.000, FP=4, FN=0, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 35 |
+
macro F1 delta vs random baseline: +0.007
|
| 36 |
+
- source_ablation_drop_runtime_and_ml_logs: rows=2905, macro F1=0.893, large precision=0.987, large recall=1.000, FP=8, FN=0, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 4, '0.7_to_0.9': 44}
|
| 37 |
+
macro F1 delta vs random baseline: -0.089
|
| 38 |
+
- source_ablation_drop_gpu_telemetry: rows=2905, macro F1=0.972, large precision=0.994, large recall=0.997, FP=4, FN=2, middle p bins={'0.1_to_0.3': 0, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 39 |
+
macro F1 delta vs random baseline: -0.010
|
| 40 |
+
- source_ablation_drop_power: rows=2905, macro F1=0.972, large precision=0.993, large recall=0.993, FP=4, FN=4, middle p bins={'0.1_to_0.3': 4, '0.3_to_0.7': 0, '0.7_to_0.9': 0}
|
| 41 |
+
macro F1 delta vs random baseline: -0.010
|
| 42 |
+
- source_ablation_drop_storage: rows=2905, macro F1=0.971, large precision=0.988, large recall=0.966, FP=7, FN=21, middle p bins={'0.1_to_0.3': 13, '0.3_to_0.7': 15, '0.7_to_0.9': 8}
|
| 43 |
+
macro F1 delta vs random baseline: -0.011
|
| 44 |
+
|
| 45 |
+
## Interpretation
|
| 46 |
+
|
| 47 |
+
- These are stress diagnostics, not tuned headline metrics.
|
| 48 |
+
- Family holdouts identify hard families that still fail under scenario generalization.
|
| 49 |
+
- Source ablations should degrade performance when the dropped layer carries independent evidence.
|
| 50 |
+
- Middle probability bins indicate whether v1 has less all-or-nothing large-training probability mass than v0.
|
data/model_runs/synthetic_v1_baseline/validation_summary.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Validation Summary
|
| 2 |
+
|
| 3 |
+
Feature table: `data/synthetic_v1/features/window_features_all.csv`
|
| 4 |
+
|
| 5 |
+
## Dataset Validation Before Training
|
| 6 |
+
|
| 7 |
+
- `synthetic_dataset_validator`: return code 0; PASS
|
| 8 |
+
- `public_dataset_validator`: return code 0; PASS
|
| 9 |
+
|
| 10 |
+
## Required Artifacts
|
| 11 |
+
|
| 12 |
+
- `README.md`: present
|
| 13 |
+
- `manifest.json`: present
|
| 14 |
+
- `model.joblib`: present
|
| 15 |
+
- `preprocessing.joblib`: present
|
| 16 |
+
- `feature_columns.json`: present
|
| 17 |
+
- `excluded_columns.json`: present
|
| 18 |
+
- `split_manifest.json`: present
|
| 19 |
+
- `metrics.json`: present
|
| 20 |
+
- `calibration_metrics.json`: present
|
| 21 |
+
- `confusion_matrix.csv`: present
|
| 22 |
+
- `classification_report.json`: present
|
| 23 |
+
- `predictions_test.csv`: present
|
| 24 |
+
- `predictions_all.csv`: present
|
| 25 |
+
- `feature_importance.csv`: present
|
| 26 |
+
- `evidence_audit_sample.csv`: present
|
| 27 |
+
- `validation_summary.md`: present
|
| 28 |
+
|
| 29 |
+
## Test Metrics
|
| 30 |
+
|
| 31 |
+
- Accuracy: 0.9725
|
| 32 |
+
- Macro F1: 0.9781
|
| 33 |
+
- Label 3/4 precision: 0.9935
|
| 34 |
+
- Label 3/4 recall: 0.9967
|
| 35 |
+
- `p_large_training >= 0.5` precision: 0.9935
|
| 36 |
+
- `p_large_training >= 0.5` recall: 0.9967
|
| 37 |
+
|
| 38 |
+
## Governance Checks
|
| 39 |
+
|
| 40 |
+
- Capacity gate applied to post-processed probabilities.
|
| 41 |
+
- Negative certification confidence is `p_label_0 * min_critical_coverage`.
|
| 42 |
+
- Integrity warnings are reported separately from positive training evidence.
|
| 43 |
+
- Raw model probabilities are retained as `raw_p_label_0` through `raw_p_label_4`.
|
src/datacenter_verification_api/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Live inference API for the datacenter verification demo."""
|
| 2 |
+
|
| 3 |
+
__all__ = ["__version__"]
|
| 4 |
+
|
| 5 |
+
__version__ = "0.1.0"
|
src/datacenter_verification_api/app.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FastAPI app for live datacenter verification model inference."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from contextlib import asynccontextmanager
|
| 7 |
+
from typing import AsyncIterator
|
| 8 |
+
|
| 9 |
+
from fastapi import FastAPI, HTTPException
|
| 10 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
+
|
| 12 |
+
from src.datacenter_verification_api import __version__
|
| 13 |
+
from src.datacenter_verification_api.model_service import ModelService
|
| 14 |
+
from src.datacenter_verification_api.schemas import (
|
| 15 |
+
BatchPredictRequest,
|
| 16 |
+
BatchPredictResponse,
|
| 17 |
+
HealthResponse,
|
| 18 |
+
MetadataResponse,
|
| 19 |
+
PredictRequest,
|
| 20 |
+
PredictResponse,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
DEFAULT_ALLOWED_ORIGINS = [
|
| 25 |
+
"https://idacy.github.io",
|
| 26 |
+
"http://localhost:8000",
|
| 27 |
+
"http://localhost:5173",
|
| 28 |
+
"http://localhost:7860",
|
| 29 |
+
"http://127.0.0.1:8000",
|
| 30 |
+
"http://127.0.0.1:5173",
|
| 31 |
+
"http://127.0.0.1:7860",
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
service: ModelService | None = None
|
| 35 |
+
load_error: str | None = None
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def parse_allowed_origins() -> list[str]:
|
| 39 |
+
raw = os.getenv("DCV_ALLOWED_ORIGINS", "")
|
| 40 |
+
if not raw.strip():
|
| 41 |
+
return DEFAULT_ALLOWED_ORIGINS
|
| 42 |
+
return [item.strip() for item in raw.split(",") if item.strip()]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@asynccontextmanager
|
| 46 |
+
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
|
| 47 |
+
global service, load_error
|
| 48 |
+
try:
|
| 49 |
+
service = ModelService.from_env()
|
| 50 |
+
load_error = None
|
| 51 |
+
except Exception as exc: # pragma: no cover - exercised by deployment misconfiguration
|
| 52 |
+
service = None
|
| 53 |
+
load_error = str(exc)
|
| 54 |
+
yield
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
app = FastAPI(
|
| 58 |
+
title="Datacenter Verification Live Inference API",
|
| 59 |
+
version=__version__,
|
| 60 |
+
lifespan=lifespan,
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
allowed_origins = parse_allowed_origins()
|
| 64 |
+
app.add_middleware(
|
| 65 |
+
CORSMiddleware,
|
| 66 |
+
allow_origins=["*"] if "*" in allowed_origins else allowed_origins,
|
| 67 |
+
allow_credentials=False,
|
| 68 |
+
allow_methods=["GET", "POST", "OPTIONS"],
|
| 69 |
+
allow_headers=["*"],
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def require_service() -> ModelService:
|
| 74 |
+
if service is None:
|
| 75 |
+
detail = "model service is not loaded"
|
| 76 |
+
if load_error:
|
| 77 |
+
detail = f"{detail}: {load_error}"
|
| 78 |
+
raise HTTPException(status_code=503, detail=detail)
|
| 79 |
+
return service
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@app.get("/health", response_model=HealthResponse)
|
| 83 |
+
def health() -> HealthResponse:
|
| 84 |
+
loaded = service is not None
|
| 85 |
+
return HealthResponse(
|
| 86 |
+
status="ok" if loaded else "error",
|
| 87 |
+
model_loaded=loaded,
|
| 88 |
+
api_version=__version__,
|
| 89 |
+
model_run_id=service.model_run_id if service else None,
|
| 90 |
+
dataset_id=service.dataset_id if service else None,
|
| 91 |
+
feature_count=len(service.feature_columns) if service else 0,
|
| 92 |
+
base_row_lookup_enabled=bool(service.feature_lookup) if service else False,
|
| 93 |
+
error=load_error,
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@app.get("/metadata", response_model=MetadataResponse)
|
| 98 |
+
def metadata() -> MetadataResponse:
|
| 99 |
+
return require_service().metadata()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@app.post("/predict", response_model=PredictResponse)
|
| 103 |
+
def predict(request: PredictRequest) -> PredictResponse:
|
| 104 |
+
return require_service().predict(request)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@app.post("/predict/batch", response_model=BatchPredictResponse)
|
| 108 |
+
def predict_batch(request: BatchPredictRequest) -> BatchPredictResponse:
|
| 109 |
+
loaded = require_service()
|
| 110 |
+
predictions = [loaded.predict(item) for item in request.requests]
|
| 111 |
+
return BatchPredictResponse(model_run_id=loaded.model_run_id, predictions=predictions)
|
src/datacenter_verification_api/feature_completion.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Feature normalization and dependent-feature completion for live inference."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
import math
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pandas as pd
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
NULL_STRINGS = {"", "nan", "none", "null", "undefined", "na", "n/a", "<na>"}
|
| 15 |
+
|
| 16 |
+
FRACTION_COLUMNS = {
|
| 17 |
+
"o1_homogeneous_high_end_fraction",
|
| 18 |
+
"o1_non_partitioned_fraction",
|
| 19 |
+
"o2_concurrency_fraction_domain",
|
| 20 |
+
"o3_training_sku_fraction",
|
| 21 |
+
"o3_billing_continuity_score",
|
| 22 |
+
"o4_gpu_util_duty_gt_70",
|
| 23 |
+
"o4_sm_tensor_active_p95",
|
| 24 |
+
"o4_hbm_used_fraction_p50",
|
| 25 |
+
"o4_hbm_bandwidth_active_p95",
|
| 26 |
+
"o4_gpu_power_fraction_p95",
|
| 27 |
+
"o4_hbm_pressure_duration_fraction",
|
| 28 |
+
"o4_power_cap_active_fraction",
|
| 29 |
+
"o4_thermal_throttle_fraction",
|
| 30 |
+
"o5_kernel_training_motif_score",
|
| 31 |
+
"o5_tensor_throughput_ratio",
|
| 32 |
+
"o6_nvlink_util_p95",
|
| 33 |
+
"o6_nvlink_periodicity_score",
|
| 34 |
+
"o7_scaleout_port_util_p95",
|
| 35 |
+
"o7_collective_periodicity_score",
|
| 36 |
+
"o7_burst_duty_cycle",
|
| 37 |
+
"o7_job_to_port_mapping_coverage",
|
| 38 |
+
"o7_flow_entropy_score",
|
| 39 |
+
"o7_cross_section_sync_score",
|
| 40 |
+
"o7_collective_jitter_score",
|
| 41 |
+
"o7_storage_traffic_fraction",
|
| 42 |
+
"o7_inference_fanout_score",
|
| 43 |
+
"o7_account_flow_linkage_confidence",
|
| 44 |
+
"o8_rack_power_fraction_p95",
|
| 45 |
+
"o8_power_cv",
|
| 46 |
+
"o8_power_cap_or_curtailment_active",
|
| 47 |
+
"o8_unattributed_power_fraction",
|
| 48 |
+
"o9_cooling_flow_duty",
|
| 49 |
+
"o10_rank_stability_score",
|
| 50 |
+
"o10_runtime_metadata_confidence",
|
| 51 |
+
"o11_checkpoint_periodicity_score",
|
| 52 |
+
"o11_read_write_training_pattern_score",
|
| 53 |
+
"o11_checkpoint_jitter_score",
|
| 54 |
+
"o11_artifact_write_pattern_score",
|
| 55 |
+
"o11_dataloader_read_pattern_score",
|
| 56 |
+
"o11_backup_or_replication_pattern_score",
|
| 57 |
+
"o11_storage_cotraffic_score",
|
| 58 |
+
"o12_log_completeness_fraction",
|
| 59 |
+
"o12_declaration_consistency_score",
|
| 60 |
+
"o13_attestation_valid_fraction",
|
| 61 |
+
"o13_confidential_compute_mode_fraction",
|
| 62 |
+
"o13_collector_measurement_valid",
|
| 63 |
+
"o14_min_critical_coverage",
|
| 64 |
+
"o14_gap_fraction_critical",
|
| 65 |
+
"o16_probe_throughput_ratio_min",
|
| 66 |
+
"o17_energy_contract_alignment_score",
|
| 67 |
+
"o17_network_provider_utilization_score",
|
| 68 |
+
"o17_procurement_or_maintenance_explanation_score",
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
for index in range(1, 18):
|
| 72 |
+
FRACTION_COLUMNS.add(f"o{index}_coverage_fraction")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@dataclass
|
| 76 |
+
class FeatureSchema:
|
| 77 |
+
"""Column type hints inferred from the training feature table."""
|
| 78 |
+
|
| 79 |
+
feature_columns: list[str]
|
| 80 |
+
numeric_columns: set[str] = field(default_factory=set)
|
| 81 |
+
boolean_columns: set[str] = field(default_factory=set)
|
| 82 |
+
|
| 83 |
+
@classmethod
|
| 84 |
+
def from_frame(cls, feature_columns: list[str], frame: pd.DataFrame | None) -> "FeatureSchema":
|
| 85 |
+
numeric_columns: set[str] = set()
|
| 86 |
+
boolean_columns: set[str] = set()
|
| 87 |
+
if frame is not None:
|
| 88 |
+
for column in feature_columns:
|
| 89 |
+
if column not in frame.columns:
|
| 90 |
+
continue
|
| 91 |
+
dtype = frame[column].dtype
|
| 92 |
+
if pd.api.types.is_bool_dtype(dtype):
|
| 93 |
+
boolean_columns.add(column)
|
| 94 |
+
numeric_columns.add(column)
|
| 95 |
+
elif pd.api.types.is_numeric_dtype(dtype):
|
| 96 |
+
numeric_columns.add(column)
|
| 97 |
+
return cls(
|
| 98 |
+
feature_columns=list(feature_columns),
|
| 99 |
+
numeric_columns=numeric_columns,
|
| 100 |
+
boolean_columns=boolean_columns,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def is_missing(value: Any) -> bool:
|
| 105 |
+
if value is None or value is pd.NA:
|
| 106 |
+
return True
|
| 107 |
+
if isinstance(value, str):
|
| 108 |
+
return value.strip().lower() in NULL_STRINGS
|
| 109 |
+
if isinstance(value, (float, np.floating)):
|
| 110 |
+
return math.isnan(float(value))
|
| 111 |
+
try:
|
| 112 |
+
return bool(pd.isna(value))
|
| 113 |
+
except (TypeError, ValueError):
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def jsonable(value: Any) -> Any:
|
| 118 |
+
if is_missing(value):
|
| 119 |
+
return None
|
| 120 |
+
if isinstance(value, (np.integer,)):
|
| 121 |
+
return int(value)
|
| 122 |
+
if isinstance(value, (np.floating,)):
|
| 123 |
+
return float(value)
|
| 124 |
+
if isinstance(value, (np.bool_,)):
|
| 125 |
+
return bool(value)
|
| 126 |
+
if isinstance(value, pd.Timestamp):
|
| 127 |
+
return value.isoformat()
|
| 128 |
+
return value
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def normalize_value(value: Any, column: str | None = None, schema: FeatureSchema | None = None) -> Any:
|
| 132 |
+
if is_missing(value):
|
| 133 |
+
return None
|
| 134 |
+
if isinstance(value, str):
|
| 135 |
+
stripped = value.strip()
|
| 136 |
+
lowered = stripped.lower()
|
| 137 |
+
if lowered in {"true", "t", "yes", "y"}:
|
| 138 |
+
return True
|
| 139 |
+
if lowered in {"false", "f", "no", "n"}:
|
| 140 |
+
return False
|
| 141 |
+
if lowered in {"1", "0"} and schema and column in schema.boolean_columns:
|
| 142 |
+
return lowered == "1"
|
| 143 |
+
if schema and column in schema.numeric_columns:
|
| 144 |
+
try:
|
| 145 |
+
number = float(stripped)
|
| 146 |
+
except ValueError:
|
| 147 |
+
return stripped
|
| 148 |
+
return int(number) if number.is_integer() else number
|
| 149 |
+
return stripped
|
| 150 |
+
if schema and column in schema.boolean_columns:
|
| 151 |
+
if isinstance(value, (bool, np.bool_)):
|
| 152 |
+
return bool(value)
|
| 153 |
+
if isinstance(value, (int, float, np.integer, np.floating)) and not is_missing(value):
|
| 154 |
+
return bool(value)
|
| 155 |
+
if schema and column in schema.numeric_columns:
|
| 156 |
+
try:
|
| 157 |
+
number = float(value)
|
| 158 |
+
except (TypeError, ValueError):
|
| 159 |
+
return value
|
| 160 |
+
if math.isnan(number):
|
| 161 |
+
return None
|
| 162 |
+
return int(number) if number.is_integer() else number
|
| 163 |
+
return jsonable(value)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def normalize_mapping(values: dict[str, Any] | None, schema: FeatureSchema) -> dict[str, Any]:
|
| 167 |
+
if not values:
|
| 168 |
+
return {}
|
| 169 |
+
return {str(key): normalize_value(value, str(key), schema) for key, value in values.items()}
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def number_or_none(value: Any) -> float | None:
|
| 173 |
+
if is_missing(value):
|
| 174 |
+
return None
|
| 175 |
+
try:
|
| 176 |
+
number = float(value)
|
| 177 |
+
except (TypeError, ValueError):
|
| 178 |
+
return None
|
| 179 |
+
if math.isnan(number):
|
| 180 |
+
return None
|
| 181 |
+
return number
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def bool_or_false(value: Any) -> bool:
|
| 185 |
+
if is_missing(value):
|
| 186 |
+
return False
|
| 187 |
+
if isinstance(value, (bool, np.bool_)):
|
| 188 |
+
return bool(value)
|
| 189 |
+
text = str(value).strip().lower()
|
| 190 |
+
if text in {"true", "t", "1", "yes", "y"}:
|
| 191 |
+
return True
|
| 192 |
+
if text in {"false", "f", "0", "no", "n"}:
|
| 193 |
+
return False
|
| 194 |
+
return False
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def set_if_changed(row: dict[str, Any], key: str, value: Any, warnings: list[str], derived: list[str]) -> None:
|
| 198 |
+
old = row.get(key)
|
| 199 |
+
normalized = jsonable(value)
|
| 200 |
+
old_normalized = jsonable(old)
|
| 201 |
+
if old_normalized != normalized:
|
| 202 |
+
row[key] = normalized
|
| 203 |
+
derived.append(key)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def clamp_fraction(value: Any) -> float | None:
|
| 207 |
+
number = number_or_none(value)
|
| 208 |
+
if number is None:
|
| 209 |
+
return None
|
| 210 |
+
return min(1.0, max(0.0, number))
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def complete_features(
|
| 214 |
+
row: dict[str, Any],
|
| 215 |
+
changed_keys: set[str],
|
| 216 |
+
*,
|
| 217 |
+
has_base_row: bool,
|
| 218 |
+
derive: bool,
|
| 219 |
+
) -> tuple[dict[str, Any], list[str]]:
|
| 220 |
+
"""Return a completed row and warnings for coherent live inference.
|
| 221 |
+
|
| 222 |
+
Base-row predictions must remain byte-for-byte close to the exported model
|
| 223 |
+
replay, so dependent features are recomputed for base rows only when an
|
| 224 |
+
edited input can affect them. Rows without a base use every available input
|
| 225 |
+
to derive minimal context.
|
| 226 |
+
"""
|
| 227 |
+
|
| 228 |
+
out = dict(row)
|
| 229 |
+
warnings: list[str] = []
|
| 230 |
+
derived: list[str] = []
|
| 231 |
+
|
| 232 |
+
if not has_base_row:
|
| 233 |
+
if is_missing(out.get("scope_type")):
|
| 234 |
+
out["scope_type"] = "site"
|
| 235 |
+
if is_missing(out.get("window_length_seconds")):
|
| 236 |
+
out["window_length_seconds"] = 3600
|
| 237 |
+
if is_missing(out.get("capacity_possible")):
|
| 238 |
+
out["capacity_possible"] = False
|
| 239 |
+
|
| 240 |
+
if not derive:
|
| 241 |
+
return out, warnings
|
| 242 |
+
|
| 243 |
+
should_derive_all = not has_base_row
|
| 244 |
+
|
| 245 |
+
allocation = number_or_none(out.get("o2_max_concurrent_normalized_gpus"))
|
| 246 |
+
duration = number_or_none(out.get("o2_allocation_duration_hours"))
|
| 247 |
+
capacity = number_or_none(out.get("o1_normalized_h100e_capacity"))
|
| 248 |
+
contiguous = number_or_none(out.get("o1_largest_contiguous_domain_gpus"))
|
| 249 |
+
|
| 250 |
+
ratio_inputs_changed = bool(
|
| 251 |
+
{"o2_max_concurrent_normalized_gpus", "o2_allocation_duration_hours"} & changed_keys
|
| 252 |
+
)
|
| 253 |
+
if (should_derive_all or ratio_inputs_changed) and allocation is not None and duration is not None:
|
| 254 |
+
ratio = (allocation * duration) / (512 * 24)
|
| 255 |
+
set_if_changed(out, "o2_gpu_hours_policy_ratio", ratio, warnings, derived)
|
| 256 |
+
set_if_changed(out, "policy_compute_ratio", ratio, warnings, derived)
|
| 257 |
+
|
| 258 |
+
concurrency_inputs_changed = bool(
|
| 259 |
+
{"o2_max_concurrent_normalized_gpus", "o1_normalized_h100e_capacity"} & changed_keys
|
| 260 |
+
)
|
| 261 |
+
if (should_derive_all or concurrency_inputs_changed) and allocation is not None and capacity is not None:
|
| 262 |
+
concurrency = clamp_fraction(allocation / capacity) if capacity > 0 else 0.0
|
| 263 |
+
set_if_changed(out, "o2_concurrency_fraction_domain", concurrency, warnings, derived)
|
| 264 |
+
|
| 265 |
+
if (should_derive_all or "o2_max_concurrent_normalized_gpus" in changed_keys) and allocation is not None:
|
| 266 |
+
rounded_allocation = int(round(allocation))
|
| 267 |
+
set_if_changed(out, "o10_world_size", rounded_allocation, warnings, derived)
|
| 268 |
+
set_if_changed(out, "o10_same_image_gpu_count", rounded_allocation, warnings, derived)
|
| 269 |
+
|
| 270 |
+
capacity_inputs_changed = bool(
|
| 271 |
+
{"o1_normalized_h100e_capacity", "o1_largest_contiguous_domain_gpus"} & changed_keys
|
| 272 |
+
)
|
| 273 |
+
if should_derive_all or capacity_inputs_changed:
|
| 274 |
+
if capacity is not None and contiguous is not None:
|
| 275 |
+
set_if_changed(out, "capacity_possible", capacity >= 512 and contiguous >= 512, warnings, derived)
|
| 276 |
+
elif not has_base_row:
|
| 277 |
+
set_if_changed(out, "capacity_possible", False, warnings, derived)
|
| 278 |
+
|
| 279 |
+
for key in sorted(FRACTION_COLUMNS & set(out)):
|
| 280 |
+
if should_derive_all or key in changed_keys:
|
| 281 |
+
clamped = clamp_fraction(out.get(key))
|
| 282 |
+
if clamped is not None:
|
| 283 |
+
set_if_changed(out, key, clamped, warnings, derived)
|
| 284 |
+
|
| 285 |
+
if derived:
|
| 286 |
+
warnings.append(f"derived fields updated from edited inputs: {', '.join(sorted(set(derived)))}")
|
| 287 |
+
|
| 288 |
+
allocation = number_or_none(out.get("o2_max_concurrent_normalized_gpus"))
|
| 289 |
+
capacity = number_or_none(out.get("o1_normalized_h100e_capacity"))
|
| 290 |
+
fabric = number_or_none(out.get("o7_synchronized_fabric_footprint"))
|
| 291 |
+
if capacity is not None and allocation is not None and allocation > capacity + 1:
|
| 292 |
+
warnings.append("allocated GPUs exceed monitored H100e capacity")
|
| 293 |
+
if capacity is not None and fabric is not None and fabric > capacity + 1:
|
| 294 |
+
warnings.append("fabric footprint exceeds monitored H100e capacity")
|
| 295 |
+
if allocation is not None and allocation > 0 and fabric is not None and fabric > allocation + 1:
|
| 296 |
+
warnings.append("fabric footprint exceeds allocated GPUs")
|
| 297 |
+
|
| 298 |
+
return out, warnings
|
src/datacenter_verification_api/model_service.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sklearn model loading and one-row inference for the live API."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
import joblib
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pandas as pd
|
| 12 |
+
|
| 13 |
+
from src.datacenter_verification_api import __version__
|
| 14 |
+
from src.datacenter_verification_api.feature_completion import (
|
| 15 |
+
FeatureSchema,
|
| 16 |
+
complete_features,
|
| 17 |
+
jsonable,
|
| 18 |
+
normalize_mapping,
|
| 19 |
+
)
|
| 20 |
+
from src.datacenter_verification_api.schemas import MetadataResponse, PredictRequest, PredictResponse
|
| 21 |
+
from src.datacenter_verification_modeling.common import (
|
| 22 |
+
LABELS,
|
| 23 |
+
PROB_COLUMNS,
|
| 24 |
+
add_governance_outputs,
|
| 25 |
+
model_input_frame,
|
| 26 |
+
probability_frame,
|
| 27 |
+
read_json,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 32 |
+
DEFAULT_MODEL_RUN_DIR = Path("data/model_runs/synthetic_v1_baseline")
|
| 33 |
+
DEFAULT_FEATURE_TABLE = Path("data/synthetic_v1/features/window_features_all.csv")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def resolve_repo_path(value: str | Path | None, default: Path) -> Path:
|
| 37 |
+
path = Path(value) if value else default
|
| 38 |
+
if path.is_absolute():
|
| 39 |
+
return path
|
| 40 |
+
return (REPO_ROOT / path).resolve()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def repo_relative(path: Path | None) -> str | None:
|
| 44 |
+
if path is None:
|
| 45 |
+
return None
|
| 46 |
+
try:
|
| 47 |
+
return path.resolve().relative_to(REPO_ROOT).as_posix()
|
| 48 |
+
except ValueError:
|
| 49 |
+
return path.name
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def read_json_if_exists(path: Path) -> Any:
|
| 53 |
+
return read_json(path) if path.exists() else {}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def split_semicolon(value: Any) -> list[str]:
|
| 57 |
+
if value is None:
|
| 58 |
+
return []
|
| 59 |
+
try:
|
| 60 |
+
if pd.isna(value):
|
| 61 |
+
return []
|
| 62 |
+
except (TypeError, ValueError):
|
| 63 |
+
pass
|
| 64 |
+
return [item.strip() for item in str(value).split(";") if item.strip()]
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class ModelService:
|
| 68 |
+
"""Loaded model artifacts plus base-row lookup for prediction requests."""
|
| 69 |
+
|
| 70 |
+
def __init__(self, model_run_dir: Path, feature_table_path: Path | None = None) -> None:
|
| 71 |
+
self.model_run_dir = model_run_dir
|
| 72 |
+
self.feature_table_path = feature_table_path
|
| 73 |
+
self.manifest = read_json_if_exists(model_run_dir / "manifest.json")
|
| 74 |
+
self.metrics = read_json_if_exists(model_run_dir / "metrics.json")
|
| 75 |
+
self.feature_columns: list[str] = read_json(model_run_dir / "feature_columns.json")
|
| 76 |
+
self.preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
|
| 77 |
+
self.model = joblib.load(model_run_dir / "model.joblib")
|
| 78 |
+
self.feature_table = self._load_feature_table(feature_table_path)
|
| 79 |
+
self.feature_schema = FeatureSchema.from_frame(self.feature_columns, self.feature_table)
|
| 80 |
+
self.feature_lookup = self._build_feature_lookup(self.feature_table)
|
| 81 |
+
self.model_run_id = str(self.manifest.get("model_run_id") or model_run_dir.name)
|
| 82 |
+
|
| 83 |
+
@classmethod
|
| 84 |
+
def from_env(cls) -> "ModelService":
|
| 85 |
+
model_run_dir = resolve_repo_path(os.getenv("DCV_MODEL_RUN_DIR"), DEFAULT_MODEL_RUN_DIR)
|
| 86 |
+
feature_table = resolve_repo_path(os.getenv("DCV_FEATURE_TABLE"), DEFAULT_FEATURE_TABLE)
|
| 87 |
+
return cls(model_run_dir=model_run_dir, feature_table_path=feature_table)
|
| 88 |
+
|
| 89 |
+
def _load_feature_table(self, path: Path | None) -> pd.DataFrame | None:
|
| 90 |
+
if path is None or not path.exists():
|
| 91 |
+
return None
|
| 92 |
+
frame = pd.read_csv(path)
|
| 93 |
+
if "feature_row_id" not in frame.columns:
|
| 94 |
+
raise ValueError(f"feature table lacks feature_row_id column: {path}")
|
| 95 |
+
return frame
|
| 96 |
+
|
| 97 |
+
def _build_feature_lookup(self, frame: pd.DataFrame | None) -> dict[str, dict[str, Any]]:
|
| 98 |
+
if frame is None:
|
| 99 |
+
return {}
|
| 100 |
+
rows: dict[str, dict[str, Any]] = {}
|
| 101 |
+
for record in frame.to_dict(orient="records"):
|
| 102 |
+
row_id = record.get("feature_row_id")
|
| 103 |
+
if row_id is not None:
|
| 104 |
+
rows[str(row_id)] = dict(record)
|
| 105 |
+
return rows
|
| 106 |
+
|
| 107 |
+
@property
|
| 108 |
+
def dataset_id(self) -> str | None:
|
| 109 |
+
training_metadata = self.manifest.get("training_metadata", {})
|
| 110 |
+
if isinstance(training_metadata, dict):
|
| 111 |
+
path = training_metadata.get("features_path") or self.manifest.get("features_path")
|
| 112 |
+
else:
|
| 113 |
+
path = self.manifest.get("features_path")
|
| 114 |
+
if isinstance(path, str) and "synthetic_v1" in path:
|
| 115 |
+
return "synthetic_v1"
|
| 116 |
+
if self.feature_table is not None and "dataset_id" in self.feature_table.columns:
|
| 117 |
+
values = self.feature_table["dataset_id"].dropna().unique()
|
| 118 |
+
if len(values):
|
| 119 |
+
return str(values[0])
|
| 120 |
+
return None
|
| 121 |
+
|
| 122 |
+
@property
|
| 123 |
+
def dataset_scale(self) -> str | None:
|
| 124 |
+
if self.feature_table is not None and "dataset_id" in self.feature_table.columns:
|
| 125 |
+
values = self.feature_table["dataset_id"].dropna().unique()
|
| 126 |
+
if len(values):
|
| 127 |
+
return str(values[0])
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
@property
|
| 131 |
+
def model_type(self) -> str | None:
|
| 132 |
+
value = self.manifest.get("model_type")
|
| 133 |
+
if isinstance(value, str):
|
| 134 |
+
return value
|
| 135 |
+
training_metadata = self.manifest.get("training_metadata", {})
|
| 136 |
+
if isinstance(training_metadata, dict):
|
| 137 |
+
inner = training_metadata.get("model_type")
|
| 138 |
+
if isinstance(inner, str):
|
| 139 |
+
return inner
|
| 140 |
+
return type(self.model).__name__
|
| 141 |
+
|
| 142 |
+
def metadata(self) -> MetadataResponse:
|
| 143 |
+
metrics_summary = self.manifest.get("metrics_summary")
|
| 144 |
+
if not isinstance(metrics_summary, dict):
|
| 145 |
+
metrics_summary = {
|
| 146 |
+
"model": self.metrics.get("model", {}),
|
| 147 |
+
"governance": self.metrics.get("governance", {}),
|
| 148 |
+
"calibration": self.metrics.get("calibration", {}),
|
| 149 |
+
}
|
| 150 |
+
return MetadataResponse(
|
| 151 |
+
api_version=__version__,
|
| 152 |
+
model_run_id=self.model_run_id,
|
| 153 |
+
model_run_dir=repo_relative(self.model_run_dir) or self.model_run_dir.name,
|
| 154 |
+
feature_table=repo_relative(self.feature_table_path),
|
| 155 |
+
dataset_id=self.dataset_id,
|
| 156 |
+
dataset_scale=self.dataset_scale,
|
| 157 |
+
model_type=self.model_type,
|
| 158 |
+
metrics_summary=metrics_summary,
|
| 159 |
+
feature_count=len(self.feature_columns),
|
| 160 |
+
feature_columns=self.feature_columns,
|
| 161 |
+
supported_labels=LABELS,
|
| 162 |
+
base_row_lookup_enabled=bool(self.feature_lookup),
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
def build_feature_row(self, request: PredictRequest) -> tuple[dict[str, Any], list[str]]:
|
| 166 |
+
warnings: list[str] = []
|
| 167 |
+
base_row = None
|
| 168 |
+
if request.feature_row_id:
|
| 169 |
+
base_row = self.feature_lookup.get(request.feature_row_id)
|
| 170 |
+
if base_row is None:
|
| 171 |
+
warnings.append(f"feature_row_id not found in base lookup: {request.feature_row_id}")
|
| 172 |
+
|
| 173 |
+
row: dict[str, Any]
|
| 174 |
+
has_base_row = base_row is not None
|
| 175 |
+
if base_row is not None:
|
| 176 |
+
row = dict(base_row)
|
| 177 |
+
else:
|
| 178 |
+
row = {column: None for column in self.feature_columns}
|
| 179 |
+
if request.feature_row_id:
|
| 180 |
+
row["feature_row_id"] = request.feature_row_id
|
| 181 |
+
|
| 182 |
+
context = normalize_mapping(request.context, self.feature_schema)
|
| 183 |
+
features = normalize_mapping(request.features, self.feature_schema)
|
| 184 |
+
changed_keys: set[str] = set()
|
| 185 |
+
|
| 186 |
+
for source_name, values in [("context", context), ("features", features)]:
|
| 187 |
+
for key, value in values.items():
|
| 188 |
+
row[key] = value
|
| 189 |
+
changed_keys.add(key)
|
| 190 |
+
if key not in self.feature_columns and key not in {"feature_row_id"}:
|
| 191 |
+
warnings.append(f"{source_name} field is not a model feature and was kept only as metadata: {key}")
|
| 192 |
+
|
| 193 |
+
for column in self.feature_columns:
|
| 194 |
+
row.setdefault(column, None)
|
| 195 |
+
|
| 196 |
+
row, completion_warnings = complete_features(
|
| 197 |
+
row,
|
| 198 |
+
changed_keys,
|
| 199 |
+
has_base_row=has_base_row,
|
| 200 |
+
derive=request.derive,
|
| 201 |
+
)
|
| 202 |
+
warnings.extend(completion_warnings)
|
| 203 |
+
|
| 204 |
+
if not has_base_row:
|
| 205 |
+
null_count = sum(1 for column in self.feature_columns if row.get(column) is None)
|
| 206 |
+
if null_count > len(self.feature_columns) // 3:
|
| 207 |
+
warnings.append(
|
| 208 |
+
f"no base row supplied; {null_count} of {len(self.feature_columns)} model features are null"
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
return row, warnings
|
| 212 |
+
|
| 213 |
+
def predict(self, request: PredictRequest) -> PredictResponse:
|
| 214 |
+
row, warnings = self.build_feature_row(request)
|
| 215 |
+
record = {
|
| 216 |
+
column: (np.nan if row.get(column) is None else row.get(column))
|
| 217 |
+
for column in sorted(set(row) | set(self.feature_columns))
|
| 218 |
+
}
|
| 219 |
+
frame = pd.DataFrame([record])
|
| 220 |
+
model_frame = model_input_frame(frame, self.feature_columns)
|
| 221 |
+
transformed = self.preprocessor.transform(model_frame)
|
| 222 |
+
raw_probabilities = probability_frame(self.model, transformed)
|
| 223 |
+
governance = add_governance_outputs(frame, raw_probabilities)
|
| 224 |
+
raw = raw_probabilities.iloc[0]
|
| 225 |
+
post = governance.iloc[0]
|
| 226 |
+
probabilities = [float(post[f"p_label_{label}"]) for label in LABELS]
|
| 227 |
+
completed_features = {
|
| 228 |
+
column: jsonable(row.get(column))
|
| 229 |
+
for column in self.feature_columns
|
| 230 |
+
if request.return_completed_features
|
| 231 |
+
}
|
| 232 |
+
return PredictResponse(
|
| 233 |
+
model_run_id=self.model_run_id,
|
| 234 |
+
feature_row_id=request.feature_row_id or jsonable(row.get("feature_row_id")),
|
| 235 |
+
predicted_label=int(post["predicted_label"]),
|
| 236 |
+
p_large_training=float(post["p_large_training"]),
|
| 237 |
+
severity_score=float(post["severity_score"]),
|
| 238 |
+
negative_certification_confidence=float(post["negative_certification_confidence"]),
|
| 239 |
+
integrity_warning=bool(post["integrity_warning"]),
|
| 240 |
+
capacity_possible=bool(post["capacity_possible"]),
|
| 241 |
+
min_critical_coverage=float(post["min_critical_coverage"]),
|
| 242 |
+
probabilities=probabilities,
|
| 243 |
+
probability_by_label={str(label): float(post[f"p_label_{label}"]) for label in LABELS},
|
| 244 |
+
raw_probability_by_label={str(label): float(raw[f"p_label_{label}"]) for label in LABELS},
|
| 245 |
+
top_evidence=split_semicolon(post["top_evidence"]),
|
| 246 |
+
critical_missing_layers=split_semicolon(post["critical_missing_layers"]),
|
| 247 |
+
input_warnings=warnings,
|
| 248 |
+
completed_features=completed_features,
|
| 249 |
+
)
|
src/datacenter_verification_api/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python >= 3.10
|
| 2 |
+
fastapi
|
| 3 |
+
uvicorn[standard]
|
| 4 |
+
pandas
|
| 5 |
+
numpy
|
| 6 |
+
scikit-learn
|
| 7 |
+
joblib
|
src/datacenter_verification_api/schemas.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pydantic schemas for the live datacenter verification inference API."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from typing import Any
|
| 6 |
+
|
| 7 |
+
from pydantic import BaseModel, ConfigDict, Field
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class PredictRequest(BaseModel):
|
| 11 |
+
"""One-row prediction request.
|
| 12 |
+
|
| 13 |
+
The feature dictionary intentionally accepts arbitrary keys because the
|
| 14 |
+
public demo exposes only a subset of the model columns and future UI
|
| 15 |
+
controls may add new fields before the API schema changes.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
model_config = ConfigDict(extra="allow")
|
| 19 |
+
|
| 20 |
+
feature_row_id: str | None = None
|
| 21 |
+
features: dict[str, Any] = Field(default_factory=dict)
|
| 22 |
+
context: dict[str, Any] = Field(default_factory=dict)
|
| 23 |
+
derive: bool = True
|
| 24 |
+
return_completed_features: bool = False
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class BatchPredictRequest(BaseModel):
|
| 28 |
+
requests: list[PredictRequest]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class HealthResponse(BaseModel):
|
| 32 |
+
status: str
|
| 33 |
+
model_loaded: bool
|
| 34 |
+
api_version: str
|
| 35 |
+
model_run_id: str | None = None
|
| 36 |
+
dataset_id: str | None = None
|
| 37 |
+
feature_count: int = 0
|
| 38 |
+
base_row_lookup_enabled: bool = False
|
| 39 |
+
error: str | None = None
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class MetadataResponse(BaseModel):
|
| 43 |
+
api_version: str
|
| 44 |
+
model_run_id: str
|
| 45 |
+
model_run_dir: str
|
| 46 |
+
feature_table: str | None = None
|
| 47 |
+
dataset_id: str | None = None
|
| 48 |
+
dataset_scale: str | None = None
|
| 49 |
+
model_type: str | None = None
|
| 50 |
+
metrics_summary: dict[str, Any] = Field(default_factory=dict)
|
| 51 |
+
feature_count: int
|
| 52 |
+
feature_columns: list[str]
|
| 53 |
+
supported_labels: list[int]
|
| 54 |
+
base_row_lookup_enabled: bool
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class PredictResponse(BaseModel):
|
| 58 |
+
mode: str = "live_model_inference"
|
| 59 |
+
model_run_id: str
|
| 60 |
+
feature_row_id: str | None = None
|
| 61 |
+
predicted_label: int
|
| 62 |
+
p_large_training: float
|
| 63 |
+
severity_score: float
|
| 64 |
+
negative_certification_confidence: float
|
| 65 |
+
integrity_warning: bool
|
| 66 |
+
capacity_possible: bool
|
| 67 |
+
min_critical_coverage: float
|
| 68 |
+
probabilities: list[float]
|
| 69 |
+
probability_by_label: dict[str, float]
|
| 70 |
+
raw_probability_by_label: dict[str, float] = Field(default_factory=dict)
|
| 71 |
+
top_evidence: list[str]
|
| 72 |
+
critical_missing_layers: list[str]
|
| 73 |
+
input_warnings: list[str] = Field(default_factory=list)
|
| 74 |
+
completed_features: dict[str, Any] = Field(default_factory=dict)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class BatchPredictResponse(BaseModel):
|
| 78 |
+
mode: str = "live_model_inference_batch"
|
| 79 |
+
model_run_id: str
|
| 80 |
+
predictions: list[PredictResponse]
|
src/datacenter_verification_api/smoke_test.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Smoke checks for the live sklearn inference service.
|
| 2 |
+
|
| 3 |
+
Run from the repository root:
|
| 4 |
+
|
| 5 |
+
python -m src.datacenter_verification_api.smoke_test
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import argparse
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import pandas as pd
|
| 14 |
+
|
| 15 |
+
from src.datacenter_verification_api.model_service import DEFAULT_MODEL_RUN_DIR, REPO_ROOT, ModelService, resolve_repo_path
|
| 16 |
+
from src.datacenter_verification_api.schemas import PredictRequest
|
| 17 |
+
from src.datacenter_verification_modeling.common import LABELS
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def parse_args() -> argparse.Namespace:
|
| 21 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 22 |
+
parser.add_argument("--model-run", type=Path, default=DEFAULT_MODEL_RUN_DIR)
|
| 23 |
+
parser.add_argument("--tolerance", type=float, default=1e-8)
|
| 24 |
+
return parser.parse_args()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def selected_prediction_rows(predictions: pd.DataFrame) -> pd.DataFrame:
|
| 28 |
+
rows = []
|
| 29 |
+
for label in LABELS:
|
| 30 |
+
part = predictions[predictions["predicted_label"].astype(int) == label]
|
| 31 |
+
if len(part):
|
| 32 |
+
rows.append(part.iloc[0])
|
| 33 |
+
if len(rows) < len(LABELS):
|
| 34 |
+
for _, row in predictions.head(len(LABELS) - len(rows)).iterrows():
|
| 35 |
+
rows.append(row)
|
| 36 |
+
return pd.DataFrame(rows).drop_duplicates("feature_row_id")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def main() -> int:
|
| 40 |
+
args = parse_args()
|
| 41 |
+
model_run_dir = resolve_repo_path(args.model_run, DEFAULT_MODEL_RUN_DIR)
|
| 42 |
+
service = ModelService(model_run_dir=model_run_dir, feature_table_path=REPO_ROOT / "data/synthetic_v1/features/window_features_all.csv")
|
| 43 |
+
predictions_path = model_run_dir / "predictions_all.csv"
|
| 44 |
+
predictions = pd.read_csv(predictions_path)
|
| 45 |
+
sample = selected_prediction_rows(predictions)
|
| 46 |
+
|
| 47 |
+
checked = 0
|
| 48 |
+
for _, expected in sample.iterrows():
|
| 49 |
+
row_id = str(expected["feature_row_id"])
|
| 50 |
+
actual = service.predict(PredictRequest(feature_row_id=row_id))
|
| 51 |
+
if actual.predicted_label != int(expected["predicted_label"]):
|
| 52 |
+
raise AssertionError(
|
| 53 |
+
f"{row_id}: predicted label mismatch {actual.predicted_label} != {int(expected['predicted_label'])}"
|
| 54 |
+
)
|
| 55 |
+
for label in LABELS:
|
| 56 |
+
expected_probability = float(expected[f"p_label_{label}"])
|
| 57 |
+
actual_probability = actual.probability_by_label[str(label)]
|
| 58 |
+
delta = abs(actual_probability - expected_probability)
|
| 59 |
+
if delta > args.tolerance:
|
| 60 |
+
raise AssertionError(
|
| 61 |
+
f"{row_id}: p_label_{label} mismatch {actual_probability} != {expected_probability} "
|
| 62 |
+
f"(delta={delta})"
|
| 63 |
+
)
|
| 64 |
+
checked += 1
|
| 65 |
+
|
| 66 |
+
edit_row_id = str(sample.iloc[-1]["feature_row_id"])
|
| 67 |
+
edited = service.predict(
|
| 68 |
+
PredictRequest(
|
| 69 |
+
feature_row_id=edit_row_id,
|
| 70 |
+
features={
|
| 71 |
+
"o2_max_concurrent_normalized_gpus": 1024,
|
| 72 |
+
"o2_allocation_duration_hours": 48,
|
| 73 |
+
"o4_gpu_util_p95": 82,
|
| 74 |
+
},
|
| 75 |
+
context={"scope_type": "topology_domain", "window_length_seconds": 3600},
|
| 76 |
+
derive=True,
|
| 77 |
+
return_completed_features=True,
|
| 78 |
+
)
|
| 79 |
+
)
|
| 80 |
+
if len(edited.probabilities) != len(LABELS):
|
| 81 |
+
raise AssertionError("edited prediction did not return five label probabilities")
|
| 82 |
+
if abs(sum(edited.probabilities) - 1.0) > 1e-8:
|
| 83 |
+
raise AssertionError("edited prediction probabilities do not sum to 1")
|
| 84 |
+
if not edited.completed_features:
|
| 85 |
+
raise AssertionError("edited prediction did not return completed_features")
|
| 86 |
+
|
| 87 |
+
print(f"PASS live inference smoke test: matched {checked} exported rows from {predictions_path}")
|
| 88 |
+
print(
|
| 89 |
+
"PASS edited prediction schema: "
|
| 90 |
+
f"row={edit_row_id} label={edited.predicted_label} p_large={edited.p_large_training:.6f}"
|
| 91 |
+
)
|
| 92 |
+
return 0
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
raise SystemExit(main())
|
src/datacenter_verification_modeling/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# datacenter verification modeling
|
| 2 |
+
|
| 3 |
+
this package trains and evaluates the first public baseline model for the synthetic v0 datacenter training-run verification dataset.
|
| 4 |
+
|
| 5 |
+
the training unit is one row from:
|
| 6 |
+
|
| 7 |
+
```text
|
| 8 |
+
data/synthetic_v0/features/window_features_all.csv
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
the model does not read raw telemetry samples, events, or snapshots directly
|
| 12 |
+
|
| 13 |
+
## outputs
|
| 14 |
+
|
| 15 |
+
the default model run is written to:
|
| 16 |
+
|
| 17 |
+
```text
|
| 18 |
+
data/model_runs/synthetic_v0_baseline/
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
it contains the fitted calibrated model, preprocessing pipeline, split manifest, feature metadata, predictions, metrics, calibration diagnostics, feature importance, and an evidence audit sample
|
| 22 |
+
|
| 23 |
+
## leakage controls
|
| 24 |
+
|
| 25 |
+
rows are split by `episode_id`, not randomly. This is required because the same latent episode appears in multiple adjacent windows. The default split is scenario-stratified at the episode level with seed `20260510`:
|
| 26 |
+
|
| 27 |
+
```text
|
| 28 |
+
train: 60%
|
| 29 |
+
validation/calibration: 20%
|
| 30 |
+
test: 20%
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
the supervised model excludes identifiers, direct labels, site id, episode id, raw manifest hashes, and synthetic-only audit columns such as
|
| 34 |
+
`latent_workload_class` and `synthetic_evidence_profile`
|
| 35 |
+
|
| 36 |
+
## model
|
| 37 |
+
|
| 38 |
+
the supervised baseline is:
|
| 39 |
+
|
| 40 |
+
```text
|
| 41 |
+
SimpleImputer + OneHotEncoder preprocessing
|
| 42 |
+
HistGradientBoostingClassifier
|
| 43 |
+
CalibratedClassifierCV on the validation split
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
the package also includes `rule_baseline.py`, a deterministic evidence-rule baseline that encodes broad study logic:
|
| 47 |
+
|
| 48 |
+
- capacity below threshold with strong coverage is negative evidence
|
| 49 |
+
- capacity alone does not prove training
|
| 50 |
+
- missing data is not zero activity
|
| 51 |
+
- integrity anomalies are warnings, not positive proof
|
| 52 |
+
- labels 3 and 4 require coherent multi-layer evidence
|
| 53 |
+
|
| 54 |
+
## commands
|
| 55 |
+
|
| 56 |
+
train, evaluate, and generate all default artifacts:
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
python src/datacenter_verification_modeling/train_model.py \
|
| 60 |
+
--features data/synthetic_v0/features/window_features_all.csv \
|
| 61 |
+
--output data/model_runs/synthetic_v0_baseline \
|
| 62 |
+
--seed 20260510
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
Evaluate an existing run:
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
python src/datacenter_verification_modeling/evaluate_model.py \
|
| 69 |
+
--model-run data/model_runs/synthetic_v0_baseline \
|
| 70 |
+
--features data/synthetic_v0/features/window_features_all.csv
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
Generate predictions from an existing run:
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
python src/datacenter_verification_modeling/predict.py \
|
| 77 |
+
--model-run data/model_runs/synthetic_v0_baseline \
|
| 78 |
+
--features data/synthetic_v0/features/window_features_all.csv \
|
| 79 |
+
--output data/model_runs/synthetic_v0_baseline/predictions_all.csv
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
Prepare split and feature metadata without training:
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
python src/datacenter_verification_modeling/prepare_features.py \
|
| 86 |
+
--features data/synthetic_v0/features/window_features_all.csv \
|
| 87 |
+
--output data/model_runs/synthetic_v0_baseline \
|
| 88 |
+
--seed 20260510
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
## governance outputs
|
| 92 |
+
|
| 93 |
+
prediction files include:
|
| 94 |
+
|
| 95 |
+
- `p_label_0` through `p_label_4`
|
| 96 |
+
- `raw_p_label_0` through `raw_p_label_4`
|
| 97 |
+
- `p_large_training`
|
| 98 |
+
- `severity_score`
|
| 99 |
+
- `capacity_possible`
|
| 100 |
+
- `negative_certification_confidence`
|
| 101 |
+
- `integrity_warning`
|
| 102 |
+
- `critical_missing_layers`
|
| 103 |
+
- `top_evidence`
|
| 104 |
+
|
| 105 |
+
the `p_label_*` columns are post-processed with the capacity gate; the `raw_p_label_*` columns preserve the calibrated model probabilities before that
|
| 106 |
+
gate
|
| 107 |
+
|
| 108 |
+
## limitations
|
| 109 |
+
|
| 110 |
+
This is a synthetic v0 prototype ! Metrics are useful for testing the pipeline, not for deployment claims. Real datacenter use would require real telemetry, controlled drills, operational calibration, and independent review
|
| 111 |
+
|
src/datacenter_verification_modeling/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Modeling pipeline for datacenter training-run verification prototypes."""
|
| 2 |
+
|
| 3 |
+
__all__ = ["__version__"]
|
| 4 |
+
|
| 5 |
+
__version__ = "0.1.0"
|
| 6 |
+
|
src/datacenter_verification_modeling/common.py
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared utilities for synthetic datacenter verification modeling baselines."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import hashlib
|
| 6 |
+
import json
|
| 7 |
+
import math
|
| 8 |
+
from datetime import datetime, timezone
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Any
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import pandas as pd
|
| 14 |
+
from sklearn.compose import ColumnTransformer
|
| 15 |
+
from sklearn.impute import SimpleImputer
|
| 16 |
+
from sklearn.pipeline import Pipeline
|
| 17 |
+
from sklearn.preprocessing import OneHotEncoder
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
DEFAULT_SEED = 20260510
|
| 21 |
+
LABELS = [0, 1, 2, 3, 4]
|
| 22 |
+
SPLITS = ["train", "validation", "test"]
|
| 23 |
+
PROB_COLUMNS = [f"p_label_{label}" for label in LABELS]
|
| 24 |
+
RAW_PROB_COLUMNS = [f"raw_p_label_{label}" for label in LABELS]
|
| 25 |
+
|
| 26 |
+
BASE_EXCLUDED_COLUMNS = [
|
| 27 |
+
"feature_row_id",
|
| 28 |
+
"dataset_id",
|
| 29 |
+
"seed",
|
| 30 |
+
"site_id",
|
| 31 |
+
"scope_id_hash",
|
| 32 |
+
"window_start",
|
| 33 |
+
"window_end",
|
| 34 |
+
"episode_id",
|
| 35 |
+
"label_0_to_4",
|
| 36 |
+
"label_confidence",
|
| 37 |
+
"label_reason",
|
| 38 |
+
"label_source",
|
| 39 |
+
"raw_input_manifest_hash",
|
| 40 |
+
"latent_workload_class",
|
| 41 |
+
"scenario_family",
|
| 42 |
+
"scenario_variant",
|
| 43 |
+
"evidence_recipe_id",
|
| 44 |
+
"counterfactual_group_id",
|
| 45 |
+
"synthetic_counterfactual_role",
|
| 46 |
+
"data_quality_regime",
|
| 47 |
+
"privacy_tier",
|
| 48 |
+
"collector_profile",
|
| 49 |
+
"topology_class",
|
| 50 |
+
"temporal_phase",
|
| 51 |
+
"synthetic_hard_case_tags",
|
| 52 |
+
"synthetic_evidence_profile",
|
| 53 |
+
"capacity_evidence_only",
|
| 54 |
+
"integrity_evidence_only",
|
| 55 |
+
"physical_evidence_only",
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
VERSION_COLUMNS_EXCLUDED = [
|
| 59 |
+
"feature_pipeline_version",
|
| 60 |
+
"policy_threshold_version",
|
| 61 |
+
"hardware_normalization_version",
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
CRITICAL_COVERAGE_COLUMNS = [
|
| 65 |
+
"o1_coverage_fraction",
|
| 66 |
+
"o2_coverage_fraction",
|
| 67 |
+
"o4_coverage_fraction",
|
| 68 |
+
"o7_coverage_fraction",
|
| 69 |
+
"o8_coverage_fraction",
|
| 70 |
+
"o14_coverage_fraction",
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
CRITICAL_MISSING_REASON_COLUMNS = {
|
| 74 |
+
"o1_coverage_fraction": "o1_missing_reason",
|
| 75 |
+
"o2_coverage_fraction": "o2_missing_reason",
|
| 76 |
+
"o4_coverage_fraction": "o4_missing_reason",
|
| 77 |
+
"o7_coverage_fraction": "o7_missing_reason",
|
| 78 |
+
"o8_coverage_fraction": "o8_missing_reason",
|
| 79 |
+
"o14_coverage_fraction": "o14_missing_reason",
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
SELECTED_AUDIT_FEATURES = [
|
| 83 |
+
"policy_compute_ratio",
|
| 84 |
+
"o2_max_concurrent_normalized_gpus",
|
| 85 |
+
"o2_allocation_duration_hours",
|
| 86 |
+
"o2_gpu_hours_policy_ratio",
|
| 87 |
+
"o4_gpu_util_p95",
|
| 88 |
+
"o4_gpu_util_duty_gt_70",
|
| 89 |
+
"o7_synchronized_fabric_footprint",
|
| 90 |
+
"o7_collective_periodicity_score",
|
| 91 |
+
"o8_rack_power_fraction_p95",
|
| 92 |
+
"o10_runtime_framework_class",
|
| 93 |
+
"o11_checkpoint_periodicity_score",
|
| 94 |
+
"o12_signed_ml_logs_present",
|
| 95 |
+
"o12_declared_parameter_count_b",
|
| 96 |
+
"o14_min_critical_coverage",
|
| 97 |
+
"o14_gap_fraction_critical",
|
| 98 |
+
"o13_confidential_compute_mode_fraction",
|
| 99 |
+
"o2_elastic_resize_count",
|
| 100 |
+
"o2_preemption_restart_count",
|
| 101 |
+
"o2_account_linkage_confidence",
|
| 102 |
+
"o4_hbm_pressure_duration_fraction",
|
| 103 |
+
"o4_power_cap_active_fraction",
|
| 104 |
+
"o7_account_flow_linkage_confidence",
|
| 105 |
+
"o10_runtime_metadata_confidence",
|
| 106 |
+
"o11_artifact_write_pattern_score",
|
| 107 |
+
"o11_dataloader_read_pattern_score",
|
| 108 |
+
"o12_log_delivery_delay_hours",
|
| 109 |
+
"o12_log_completeness_fraction",
|
| 110 |
+
"o4_missing_reason",
|
| 111 |
+
"o7_missing_reason",
|
| 112 |
+
"o12_missing_reason",
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def utc_now_iso() -> str:
|
| 117 |
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def ensure_dir(path: Path) -> Path:
|
| 121 |
+
path.mkdir(parents=True, exist_ok=True)
|
| 122 |
+
return path
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def to_jsonable(value: Any) -> Any:
|
| 126 |
+
if isinstance(value, (np.integer,)):
|
| 127 |
+
return int(value)
|
| 128 |
+
if isinstance(value, (np.floating,)):
|
| 129 |
+
if math.isnan(float(value)):
|
| 130 |
+
return None
|
| 131 |
+
return float(value)
|
| 132 |
+
if isinstance(value, (np.bool_,)):
|
| 133 |
+
return bool(value)
|
| 134 |
+
if isinstance(value, np.ndarray):
|
| 135 |
+
return [to_jsonable(item) for item in value.tolist()]
|
| 136 |
+
if isinstance(value, pd.Series):
|
| 137 |
+
return [to_jsonable(item) for item in value.tolist()]
|
| 138 |
+
if isinstance(value, pd.Timestamp):
|
| 139 |
+
return value.isoformat()
|
| 140 |
+
if value is pd.NA:
|
| 141 |
+
return None
|
| 142 |
+
return value
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def write_json(path: Path, payload: Any) -> None:
|
| 146 |
+
path.write_text(json.dumps(payload, indent=2, sort_keys=True, default=to_jsonable) + "\n", encoding="utf-8")
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def read_json(path: Path) -> Any:
|
| 150 |
+
return json.loads(path.read_text(encoding="utf-8"))
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def sha256_file(path: Path) -> str:
|
| 154 |
+
digest = hashlib.sha256()
|
| 155 |
+
with path.open("rb") as handle:
|
| 156 |
+
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
| 157 |
+
digest.update(chunk)
|
| 158 |
+
return f"sha256:{digest.hexdigest()}"
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def as_bool(value: Any, default: bool = False) -> bool:
|
| 162 |
+
if value is None:
|
| 163 |
+
return default
|
| 164 |
+
try:
|
| 165 |
+
if pd.isna(value):
|
| 166 |
+
return default
|
| 167 |
+
except TypeError:
|
| 168 |
+
pass
|
| 169 |
+
if isinstance(value, (bool, np.bool_)):
|
| 170 |
+
return bool(value)
|
| 171 |
+
text = str(value).strip().lower()
|
| 172 |
+
if text in {"true", "t", "1", "yes", "y"}:
|
| 173 |
+
return True
|
| 174 |
+
if text in {"false", "f", "0", "no", "n"}:
|
| 175 |
+
return False
|
| 176 |
+
return default
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def as_float(value: Any, default: float = 0.0) -> float:
|
| 180 |
+
if value is None:
|
| 181 |
+
return default
|
| 182 |
+
try:
|
| 183 |
+
if pd.isna(value):
|
| 184 |
+
return default
|
| 185 |
+
except TypeError:
|
| 186 |
+
pass
|
| 187 |
+
try:
|
| 188 |
+
return float(value)
|
| 189 |
+
except (TypeError, ValueError):
|
| 190 |
+
return default
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def row_float(row: pd.Series, column: str, default: float = 0.0) -> float:
|
| 194 |
+
return as_float(row[column], default) if column in row else default
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def row_bool(row: pd.Series, column: str, default: bool = False) -> bool:
|
| 198 |
+
return as_bool(row[column], default) if column in row else default
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def load_feature_table(path: Path) -> pd.DataFrame:
|
| 202 |
+
df = pd.read_csv(path)
|
| 203 |
+
required = {
|
| 204 |
+
"feature_row_id",
|
| 205 |
+
"episode_id",
|
| 206 |
+
"latent_workload_class",
|
| 207 |
+
"label_0_to_4",
|
| 208 |
+
"capacity_possible",
|
| 209 |
+
}
|
| 210 |
+
missing = sorted(required - set(df.columns))
|
| 211 |
+
if missing:
|
| 212 |
+
raise ValueError(f"feature table missing required columns: {missing}")
|
| 213 |
+
df["label_0_to_4"] = df["label_0_to_4"].astype(int)
|
| 214 |
+
return df
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def derive_dataset_dir(features_path: Path) -> Path:
|
| 218 |
+
path = features_path.resolve()
|
| 219 |
+
if path.parent.name == "features":
|
| 220 |
+
return path.parent.parent
|
| 221 |
+
return path.parent
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def split_summary(df: pd.DataFrame) -> dict[str, Any]:
|
| 225 |
+
summary: dict[str, Any] = {}
|
| 226 |
+
for split in SPLITS:
|
| 227 |
+
part = df[df["split"] == split]
|
| 228 |
+
summary[split] = {
|
| 229 |
+
"rows": int(len(part)),
|
| 230 |
+
"episodes": int(part["episode_id"].nunique()),
|
| 231 |
+
"label_distribution": {
|
| 232 |
+
str(label): int(count)
|
| 233 |
+
for label, count in part["label_0_to_4"].value_counts().sort_index().items()
|
| 234 |
+
},
|
| 235 |
+
"scenario_distribution": {
|
| 236 |
+
str(name): int(count)
|
| 237 |
+
for name, count in part["latent_workload_class"].value_counts().sort_index().items()
|
| 238 |
+
},
|
| 239 |
+
}
|
| 240 |
+
return summary
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def make_episode_split(df: pd.DataFrame, seed: int = DEFAULT_SEED) -> tuple[pd.DataFrame, dict[str, Any]]:
|
| 244 |
+
"""Create a deterministic scenario-stratified grouped split by episode_id."""
|
| 245 |
+
episode_df = (
|
| 246 |
+
df.groupby("episode_id", as_index=False)
|
| 247 |
+
.agg(
|
| 248 |
+
latent_workload_class=("latent_workload_class", "first"),
|
| 249 |
+
label_mode=("label_0_to_4", lambda values: int(values.mode().iloc[0])),
|
| 250 |
+
label_min=("label_0_to_4", "min"),
|
| 251 |
+
label_max=("label_0_to_4", "max"),
|
| 252 |
+
row_count=("feature_row_id", "count"),
|
| 253 |
+
site_count=("site_id", "nunique") if "site_id" in df.columns else ("episode_id", "count"),
|
| 254 |
+
)
|
| 255 |
+
.sort_values(["latent_workload_class", "episode_id"])
|
| 256 |
+
.reset_index(drop=True)
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
rng = np.random.default_rng(seed)
|
| 260 |
+
assignments: dict[str, str] = {}
|
| 261 |
+
scenario_allocations: dict[str, dict[str, int]] = {}
|
| 262 |
+
for scenario, scenario_rows in episode_df.groupby("latent_workload_class", sort=True):
|
| 263 |
+
episode_ids = scenario_rows["episode_id"].to_numpy().copy()
|
| 264 |
+
rng.shuffle(episode_ids)
|
| 265 |
+
count = len(episode_ids)
|
| 266 |
+
if count >= 3:
|
| 267 |
+
validation_count = max(1, round(0.20 * count))
|
| 268 |
+
test_count = max(1, round(0.20 * count))
|
| 269 |
+
train_count = count - validation_count - test_count
|
| 270 |
+
if train_count < 1:
|
| 271 |
+
train_count = 1
|
| 272 |
+
if validation_count > test_count:
|
| 273 |
+
validation_count -= 1
|
| 274 |
+
else:
|
| 275 |
+
test_count -= 1
|
| 276 |
+
elif count == 2:
|
| 277 |
+
train_count, validation_count, test_count = 1, 1, 0
|
| 278 |
+
else:
|
| 279 |
+
train_count, validation_count, test_count = 1, 0, 0
|
| 280 |
+
|
| 281 |
+
for episode_id in episode_ids[:train_count]:
|
| 282 |
+
assignments[str(episode_id)] = "train"
|
| 283 |
+
for episode_id in episode_ids[train_count : train_count + validation_count]:
|
| 284 |
+
assignments[str(episode_id)] = "validation"
|
| 285 |
+
for episode_id in episode_ids[train_count + validation_count :]:
|
| 286 |
+
assignments[str(episode_id)] = "test"
|
| 287 |
+
scenario_allocations[str(scenario)] = {
|
| 288 |
+
"episodes": int(count),
|
| 289 |
+
"train": int(train_count),
|
| 290 |
+
"validation": int(validation_count),
|
| 291 |
+
"test": int(test_count),
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
split_df = df.copy()
|
| 295 |
+
split_df["split"] = split_df["episode_id"].map(assignments)
|
| 296 |
+
if split_df["split"].isna().any():
|
| 297 |
+
raise ValueError("episode split assignment failed for some rows")
|
| 298 |
+
|
| 299 |
+
episode_assignments = []
|
| 300 |
+
split_lookup = dict(zip(episode_df["episode_id"], episode_df.index, strict=False))
|
| 301 |
+
for episode_id, split in sorted(assignments.items()):
|
| 302 |
+
row = episode_df.iloc[split_lookup[episode_id]]
|
| 303 |
+
episode_assignments.append(
|
| 304 |
+
{
|
| 305 |
+
"episode_id": episode_id,
|
| 306 |
+
"split": split,
|
| 307 |
+
"latent_workload_class": row["latent_workload_class"],
|
| 308 |
+
"label_mode": int(row["label_mode"]),
|
| 309 |
+
"label_min": int(row["label_min"]),
|
| 310 |
+
"label_max": int(row["label_max"]),
|
| 311 |
+
"row_count": int(row["row_count"]),
|
| 312 |
+
}
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
manifest = {
|
| 316 |
+
"seed": int(seed),
|
| 317 |
+
"method": "scenario_stratified_grouped_by_episode_id",
|
| 318 |
+
"split_fractions_requested": {"train": 0.60, "validation": 0.20, "test": 0.20},
|
| 319 |
+
"leakage_prevention": "All rows from the same episode_id are assigned to exactly one split.",
|
| 320 |
+
"scenario_allocations": scenario_allocations,
|
| 321 |
+
"summary": split_summary(split_df),
|
| 322 |
+
"episode_assignments": episode_assignments,
|
| 323 |
+
}
|
| 324 |
+
return split_df, manifest
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def apply_split_manifest(df: pd.DataFrame, split_manifest: dict[str, Any]) -> pd.DataFrame:
|
| 328 |
+
assignments = {
|
| 329 |
+
item["episode_id"]: item["split"]
|
| 330 |
+
for item in split_manifest.get("episode_assignments", [])
|
| 331 |
+
if "episode_id" in item and "split" in item
|
| 332 |
+
}
|
| 333 |
+
out = df.copy()
|
| 334 |
+
out["split"] = out["episode_id"].map(assignments).fillna("unassigned")
|
| 335 |
+
return out
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def determine_feature_columns(df: pd.DataFrame) -> tuple[list[str], dict[str, Any]]:
|
| 339 |
+
requested_exclusions = BASE_EXCLUDED_COLUMNS + VERSION_COLUMNS_EXCLUDED
|
| 340 |
+
present_exclusions = [column for column in requested_exclusions if column in df.columns]
|
| 341 |
+
missing_requested = [column for column in requested_exclusions if column not in df.columns]
|
| 342 |
+
feature_columns = [column for column in df.columns if column not in set(present_exclusions + ["split"])]
|
| 343 |
+
feature_columns = [column for column in feature_columns if not column.startswith("raw_")]
|
| 344 |
+
metadata = {
|
| 345 |
+
"requested_excluded_columns": requested_exclusions,
|
| 346 |
+
"present_excluded_columns": present_exclusions,
|
| 347 |
+
"missing_requested_excluded_columns": missing_requested,
|
| 348 |
+
"notes": [
|
| 349 |
+
"Identifier, label, direct leakage, site, and synthetic-only audit columns are excluded.",
|
| 350 |
+
"scope_type and window_length_seconds are retained because they are valid deployment-time context.",
|
| 351 |
+
"Constant version metadata columns are excluded from the baseline.",
|
| 352 |
+
],
|
| 353 |
+
}
|
| 354 |
+
return feature_columns, metadata
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def categorize_feature_columns(df: pd.DataFrame, feature_columns: list[str]) -> dict[str, list[str]]:
|
| 358 |
+
numeric_columns: list[str] = []
|
| 359 |
+
categorical_columns: list[str] = []
|
| 360 |
+
for column in feature_columns:
|
| 361 |
+
dtype = df[column].dtype
|
| 362 |
+
if pd.api.types.is_numeric_dtype(dtype) or pd.api.types.is_bool_dtype(dtype):
|
| 363 |
+
numeric_columns.append(column)
|
| 364 |
+
else:
|
| 365 |
+
categorical_columns.append(column)
|
| 366 |
+
return {"numeric": numeric_columns, "categorical": categorical_columns}
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def make_preprocessor(df: pd.DataFrame, feature_columns: list[str]) -> ColumnTransformer:
|
| 370 |
+
types = categorize_feature_columns(df, feature_columns)
|
| 371 |
+
transformers: list[tuple[str, Pipeline, list[str]]] = []
|
| 372 |
+
if types["numeric"]:
|
| 373 |
+
transformers.append(
|
| 374 |
+
(
|
| 375 |
+
"numeric",
|
| 376 |
+
Pipeline(
|
| 377 |
+
steps=[
|
| 378 |
+
("imputer", SimpleImputer(strategy="median", add_indicator=True)),
|
| 379 |
+
]
|
| 380 |
+
),
|
| 381 |
+
types["numeric"],
|
| 382 |
+
)
|
| 383 |
+
)
|
| 384 |
+
if types["categorical"]:
|
| 385 |
+
try:
|
| 386 |
+
encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False, dtype=np.float64)
|
| 387 |
+
except TypeError: # pragma: no cover - compatibility for older scikit-learn
|
| 388 |
+
encoder = OneHotEncoder(handle_unknown="ignore", sparse=False, dtype=np.float64)
|
| 389 |
+
transformers.append(
|
| 390 |
+
(
|
| 391 |
+
"categorical",
|
| 392 |
+
Pipeline(
|
| 393 |
+
steps=[
|
| 394 |
+
("imputer", SimpleImputer(strategy="constant", fill_value="__missing__")),
|
| 395 |
+
("onehot", encoder),
|
| 396 |
+
]
|
| 397 |
+
),
|
| 398 |
+
types["categorical"],
|
| 399 |
+
)
|
| 400 |
+
)
|
| 401 |
+
return ColumnTransformer(transformers=transformers, remainder="drop", verbose_feature_names_out=True)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def model_input_frame(df: pd.DataFrame, feature_columns: list[str]) -> pd.DataFrame:
|
| 405 |
+
missing = [column for column in feature_columns if column not in df.columns]
|
| 406 |
+
if missing:
|
| 407 |
+
raise ValueError(f"feature table missing model feature columns: {missing}")
|
| 408 |
+
return df.loc[:, feature_columns].copy()
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def probability_frame(model: Any, transformed_features: Any, prefix: str = "p_label") -> pd.DataFrame:
|
| 412 |
+
raw = model.predict_proba(transformed_features)
|
| 413 |
+
classes = [int(label) for label in model.classes_]
|
| 414 |
+
data = np.zeros((raw.shape[0], len(LABELS)), dtype=float)
|
| 415 |
+
for source_index, label in enumerate(classes):
|
| 416 |
+
if label in LABELS:
|
| 417 |
+
data[:, LABELS.index(label)] = raw[:, source_index]
|
| 418 |
+
data = normalize_probability_array(data)
|
| 419 |
+
return pd.DataFrame(data, columns=[f"{prefix}_{label}" for label in LABELS])
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
def normalize_probability_array(probs: np.ndarray) -> np.ndarray:
|
| 423 |
+
probs = np.clip(np.asarray(probs, dtype=float), 0.0, 1.0)
|
| 424 |
+
row_sums = probs.sum(axis=1)
|
| 425 |
+
empty = row_sums <= 0
|
| 426 |
+
if np.any(empty):
|
| 427 |
+
probs[empty, :] = 1.0 / len(LABELS)
|
| 428 |
+
row_sums = probs.sum(axis=1)
|
| 429 |
+
return probs / row_sums[:, None]
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
def minimum_critical_coverage(df: pd.DataFrame) -> pd.Series:
|
| 433 |
+
coverage_values = []
|
| 434 |
+
for column in CRITICAL_COVERAGE_COLUMNS:
|
| 435 |
+
if column in df.columns:
|
| 436 |
+
coverage_values.append(pd.to_numeric(df[column], errors="coerce").fillna(0.0).clip(0.0, 1.0))
|
| 437 |
+
else:
|
| 438 |
+
coverage_values.append(pd.Series(np.zeros(len(df)), index=df.index))
|
| 439 |
+
return pd.concat(coverage_values, axis=1).min(axis=1)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def integrity_warning_series(df: pd.DataFrame) -> pd.Series:
|
| 443 |
+
warnings = pd.Series(False, index=df.index)
|
| 444 |
+
checks = {
|
| 445 |
+
"o14_gap_fraction_critical": (0.05, "gt"),
|
| 446 |
+
"o14_min_critical_coverage": (0.80, "lt"),
|
| 447 |
+
"o13_attestation_valid_fraction": (0.90, "lt"),
|
| 448 |
+
"o13_confidential_compute_mode_fraction": (0.50, "gt"),
|
| 449 |
+
"o14_counter_reset_count": (0.0, "gt"),
|
| 450 |
+
}
|
| 451 |
+
for column, (threshold, direction) in checks.items():
|
| 452 |
+
if column not in df.columns:
|
| 453 |
+
continue
|
| 454 |
+
values = pd.to_numeric(df[column], errors="coerce").fillna(0.0)
|
| 455 |
+
if direction == "gt":
|
| 456 |
+
warnings |= values > threshold
|
| 457 |
+
else:
|
| 458 |
+
warnings |= values < threshold
|
| 459 |
+
if "o15_unapproved_physical_change_near_window" in df.columns:
|
| 460 |
+
warnings |= df["o15_unapproved_physical_change_near_window"].map(as_bool)
|
| 461 |
+
return warnings.astype(bool)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def apply_capacity_gate(df: pd.DataFrame, probabilities: pd.DataFrame, high_label_cap: float = 0.02) -> pd.DataFrame:
|
| 465 |
+
probs = probabilities.loc[:, PROB_COLUMNS].to_numpy(copy=True)
|
| 466 |
+
if "capacity_possible" not in df.columns:
|
| 467 |
+
return pd.DataFrame(normalize_probability_array(probs), columns=PROB_COLUMNS, index=probabilities.index)
|
| 468 |
+
capacity_possible = df["capacity_possible"].map(as_bool).to_numpy()
|
| 469 |
+
if "o17_external_capacity_conflict_score" in df.columns:
|
| 470 |
+
external_conflict = pd.to_numeric(df["o17_external_capacity_conflict_score"], errors="coerce").fillna(0.0).to_numpy()
|
| 471 |
+
else:
|
| 472 |
+
external_conflict = np.zeros(len(df))
|
| 473 |
+
gate_mask = (~capacity_possible) & (external_conflict < 0.5)
|
| 474 |
+
for row_index in np.where(gate_mask)[0]:
|
| 475 |
+
high = probs[row_index, 2:5].sum()
|
| 476 |
+
capped_high = min(float(high), high_label_cap)
|
| 477 |
+
if high > 0:
|
| 478 |
+
probs[row_index, 2:5] *= capped_high / high
|
| 479 |
+
low_target = 1.0 - capped_high
|
| 480 |
+
low = probs[row_index, 0:2].sum()
|
| 481 |
+
if low > 0:
|
| 482 |
+
probs[row_index, 0:2] *= low_target / low
|
| 483 |
+
else:
|
| 484 |
+
probs[row_index, 0] = low_target
|
| 485 |
+
probs[row_index, 1] = 0.0
|
| 486 |
+
return pd.DataFrame(normalize_probability_array(probs), columns=PROB_COLUMNS, index=probabilities.index)
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def critical_missing_layers_for_row(row: pd.Series) -> str:
|
| 490 |
+
layers: list[str] = []
|
| 491 |
+
for coverage_column, missing_column in CRITICAL_MISSING_REASON_COLUMNS.items():
|
| 492 |
+
coverage = row_float(row, coverage_column, 0.0)
|
| 493 |
+
reason = str(row[missing_column]) if missing_column in row and not pd.isna(row[missing_column]) else "unknown"
|
| 494 |
+
if coverage < 0.80 or reason not in {"observed", ""}:
|
| 495 |
+
observable = coverage_column.split("_", 1)[0].upper()
|
| 496 |
+
layers.append(f"{observable}:{reason}:coverage={coverage:.2f}")
|
| 497 |
+
return "; ".join(layers)
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
def top_evidence_for_row(row: pd.Series) -> str:
|
| 501 |
+
evidence: list[str] = []
|
| 502 |
+
capacity = row_bool(row, "capacity_possible")
|
| 503 |
+
external_conflict = row_float(row, "o17_external_capacity_conflict_score", 0.0)
|
| 504 |
+
allocation_gpus = row_float(row, "o2_max_concurrent_normalized_gpus")
|
| 505 |
+
allocation_hours = row_float(row, "o2_allocation_duration_hours")
|
| 506 |
+
gpu_hours_ratio = row_float(row, "o2_gpu_hours_policy_ratio")
|
| 507 |
+
gpu_util = row_float(row, "o4_gpu_util_p95")
|
| 508 |
+
tensor = row_float(row, "o4_sm_tensor_active_p95")
|
| 509 |
+
fabric_footprint = row_float(row, "o7_synchronized_fabric_footprint")
|
| 510 |
+
fabric_periodicity = row_float(row, "o7_collective_periodicity_score")
|
| 511 |
+
rack_power = row_float(row, "o8_rack_power_fraction_p95")
|
| 512 |
+
checkpoint = row_float(row, "o11_checkpoint_periodicity_score")
|
| 513 |
+
signed_logs = row_bool(row, "o12_signed_ml_logs_present")
|
| 514 |
+
min_coverage = row_float(row, "o14_min_critical_coverage", 1.0)
|
| 515 |
+
gap_fraction = row_float(row, "o14_gap_fraction_critical")
|
| 516 |
+
cc_fraction = row_float(row, "o13_confidential_compute_mode_fraction")
|
| 517 |
+
runtime = str(row["o10_runtime_framework_class"]) if "o10_runtime_framework_class" in row else ""
|
| 518 |
+
|
| 519 |
+
if not capacity and external_conflict < 0.5:
|
| 520 |
+
evidence.append("capacity below policy threshold")
|
| 521 |
+
if external_conflict >= 0.5:
|
| 522 |
+
evidence.append("external capacity conflict")
|
| 523 |
+
if allocation_gpus >= 512 or gpu_hours_ratio >= 1.0:
|
| 524 |
+
evidence.append("large allocation")
|
| 525 |
+
elif allocation_gpus >= 128:
|
| 526 |
+
evidence.append("moderate allocation")
|
| 527 |
+
if allocation_hours >= 24:
|
| 528 |
+
evidence.append("long allocation duration")
|
| 529 |
+
if gpu_util >= 70 or tensor >= 0.60:
|
| 530 |
+
evidence.append("high GPU activity")
|
| 531 |
+
if fabric_footprint >= 512 or fabric_periodicity >= 0.60:
|
| 532 |
+
evidence.append("synchronized scale-out fabric")
|
| 533 |
+
if rack_power >= 0.60:
|
| 534 |
+
evidence.append("power corroboration")
|
| 535 |
+
if "training" in runtime or "fine_tune" in runtime:
|
| 536 |
+
evidence.append("training runtime metadata")
|
| 537 |
+
if checkpoint >= 0.55:
|
| 538 |
+
evidence.append("checkpoint cadence")
|
| 539 |
+
if signed_logs:
|
| 540 |
+
evidence.append("signed ML logs")
|
| 541 |
+
if min_coverage < 0.80 or gap_fraction > 0.05:
|
| 542 |
+
evidence.append("low critical coverage")
|
| 543 |
+
if cc_fraction > 0.50 or str(row.get("o4_missing_reason", "")) == "counter_disabled_by_cc_mode":
|
| 544 |
+
evidence.append("counter disabled by CC mode")
|
| 545 |
+
if not evidence:
|
| 546 |
+
evidence.append("no strong positive evidence")
|
| 547 |
+
return "; ".join(evidence[:8])
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def add_governance_outputs(df: pd.DataFrame, raw_probabilities: pd.DataFrame) -> pd.DataFrame:
|
| 551 |
+
post_probs = apply_capacity_gate(df, raw_probabilities.loc[:, PROB_COLUMNS])
|
| 552 |
+
out = post_probs.copy()
|
| 553 |
+
prob_values = out.loc[:, PROB_COLUMNS].to_numpy()
|
| 554 |
+
predicted = np.asarray(LABELS)[np.argmax(prob_values, axis=1)]
|
| 555 |
+
out["predicted_label"] = predicted.astype(int)
|
| 556 |
+
out["p_large_training"] = out["p_label_3"] + out["p_label_4"]
|
| 557 |
+
out["severity_score"] = sum(label * out[f"p_label_{label}"] for label in LABELS)
|
| 558 |
+
min_coverage = minimum_critical_coverage(df)
|
| 559 |
+
out["min_critical_coverage"] = min_coverage
|
| 560 |
+
out["negative_certification_confidence"] = out["p_label_0"] * min_coverage
|
| 561 |
+
out["capacity_possible"] = df["capacity_possible"].map(as_bool) if "capacity_possible" in df.columns else False
|
| 562 |
+
out["integrity_warning"] = integrity_warning_series(df)
|
| 563 |
+
out["critical_missing_layers"] = df.apply(critical_missing_layers_for_row, axis=1)
|
| 564 |
+
out["top_evidence"] = df.apply(top_evidence_for_row, axis=1)
|
| 565 |
+
return out
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def build_prediction_frame(
|
| 569 |
+
df: pd.DataFrame,
|
| 570 |
+
raw_probabilities: pd.DataFrame,
|
| 571 |
+
governance_probabilities: pd.DataFrame,
|
| 572 |
+
) -> pd.DataFrame:
|
| 573 |
+
base_columns = [
|
| 574 |
+
"split",
|
| 575 |
+
"episode_id",
|
| 576 |
+
"feature_row_id",
|
| 577 |
+
"site_id",
|
| 578 |
+
"scope_type",
|
| 579 |
+
"scope_id_hash",
|
| 580 |
+
"window_start",
|
| 581 |
+
"window_end",
|
| 582 |
+
"window_length_seconds",
|
| 583 |
+
"latent_workload_class",
|
| 584 |
+
"scenario_family",
|
| 585 |
+
"scenario_variant",
|
| 586 |
+
"data_quality_regime",
|
| 587 |
+
"counterfactual_group_id",
|
| 588 |
+
"synthetic_hard_case_tags",
|
| 589 |
+
"label_0_to_4",
|
| 590 |
+
]
|
| 591 |
+
present_base_columns = [column for column in base_columns if column in df.columns]
|
| 592 |
+
out = df.loc[:, present_base_columns].copy()
|
| 593 |
+
raw = raw_probabilities.loc[:, PROB_COLUMNS].copy()
|
| 594 |
+
raw.columns = RAW_PROB_COLUMNS
|
| 595 |
+
out = pd.concat([out.reset_index(drop=True), raw.reset_index(drop=True)], axis=1)
|
| 596 |
+
governance_columns = PROB_COLUMNS + [
|
| 597 |
+
"predicted_label",
|
| 598 |
+
"p_large_training",
|
| 599 |
+
"severity_score",
|
| 600 |
+
"capacity_possible",
|
| 601 |
+
"negative_certification_confidence",
|
| 602 |
+
"integrity_warning",
|
| 603 |
+
"critical_missing_layers",
|
| 604 |
+
"top_evidence",
|
| 605 |
+
"min_critical_coverage",
|
| 606 |
+
]
|
| 607 |
+
out = pd.concat([out, governance_probabilities.loc[:, governance_columns].reset_index(drop=True)], axis=1)
|
| 608 |
+
for column in SELECTED_AUDIT_FEATURES:
|
| 609 |
+
if column in df.columns and column not in out.columns:
|
| 610 |
+
out[column] = df[column].to_numpy()
|
| 611 |
+
return out
|
src/datacenter_verification_modeling/evaluate_model.py
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate a trained synthetic datacenter verification modeling run."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
import joblib
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pandas as pd
|
| 12 |
+
from sklearn.metrics import (
|
| 13 |
+
accuracy_score,
|
| 14 |
+
brier_score_loss,
|
| 15 |
+
classification_report,
|
| 16 |
+
confusion_matrix,
|
| 17 |
+
f1_score,
|
| 18 |
+
log_loss,
|
| 19 |
+
precision_recall_fscore_support,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from .common import (
|
| 24 |
+
DEFAULT_SEED,
|
| 25 |
+
LABELS,
|
| 26 |
+
PROB_COLUMNS,
|
| 27 |
+
RAW_PROB_COLUMNS,
|
| 28 |
+
SELECTED_AUDIT_FEATURES,
|
| 29 |
+
add_governance_outputs,
|
| 30 |
+
apply_split_manifest,
|
| 31 |
+
build_prediction_frame,
|
| 32 |
+
load_feature_table,
|
| 33 |
+
minimum_critical_coverage,
|
| 34 |
+
model_input_frame,
|
| 35 |
+
probability_frame,
|
| 36 |
+
read_json,
|
| 37 |
+
sha256_file,
|
| 38 |
+
utc_now_iso,
|
| 39 |
+
write_json,
|
| 40 |
+
)
|
| 41 |
+
from .rule_baseline import predict_rule_labels
|
| 42 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 43 |
+
from common import (
|
| 44 |
+
DEFAULT_SEED,
|
| 45 |
+
LABELS,
|
| 46 |
+
PROB_COLUMNS,
|
| 47 |
+
RAW_PROB_COLUMNS,
|
| 48 |
+
SELECTED_AUDIT_FEATURES,
|
| 49 |
+
add_governance_outputs,
|
| 50 |
+
apply_split_manifest,
|
| 51 |
+
build_prediction_frame,
|
| 52 |
+
load_feature_table,
|
| 53 |
+
minimum_critical_coverage,
|
| 54 |
+
model_input_frame,
|
| 55 |
+
probability_frame,
|
| 56 |
+
read_json,
|
| 57 |
+
sha256_file,
|
| 58 |
+
utc_now_iso,
|
| 59 |
+
write_json,
|
| 60 |
+
)
|
| 61 |
+
from rule_baseline import predict_rule_labels
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
HARD_FALSE_POSITIVE_SCENARIOS = {
|
| 65 |
+
"large_batch_inference",
|
| 66 |
+
"synthetic_data_generation",
|
| 67 |
+
"hpc_mpi_simulation",
|
| 68 |
+
"nccl_benchmark",
|
| 69 |
+
"hardware_burn_in",
|
| 70 |
+
"storage_rebuild",
|
| 71 |
+
"large_etl_data_movement",
|
| 72 |
+
"reserved_but_unused_capacity",
|
| 73 |
+
"maintenance_window",
|
| 74 |
+
"large_batch_inference",
|
| 75 |
+
"model_parallel_inference",
|
| 76 |
+
"embedding_generation",
|
| 77 |
+
"synthetic_data_generation_gpu_heavy",
|
| 78 |
+
"hpc_mpi_collective",
|
| 79 |
+
"nccl_extended_benchmark",
|
| 80 |
+
"hardware_burn_in_or_thermal_soak",
|
| 81 |
+
"storage_rebuild_or_replication",
|
| 82 |
+
"large_etl_or_data_movement",
|
| 83 |
+
"distributed_database_or_graph_analytics",
|
| 84 |
+
"reserved_but_unused_capacity",
|
| 85 |
+
"maintenance_with_collector_gaps",
|
| 86 |
+
"multi_tenant_fragmented_nontraining",
|
| 87 |
+
"capacity_or_integrity_only_warning",
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
TRAINING_SCENARIOS = {
|
| 91 |
+
"large_fine_tune",
|
| 92 |
+
"pretraining",
|
| 93 |
+
"cloud_reservation_used_for_training",
|
| 94 |
+
"adversarial_fragmented_training",
|
| 95 |
+
"underclocked_long_duration_training",
|
| 96 |
+
"pretraining_standard",
|
| 97 |
+
"large_fine_tune_standard",
|
| 98 |
+
"cloud_training_redacted_runtime",
|
| 99 |
+
"training_without_semantic_logs",
|
| 100 |
+
"underclocked_energy_capped_training",
|
| 101 |
+
"elastic_preempted_training",
|
| 102 |
+
"fragmented_training_linked",
|
| 103 |
+
"sparse_or_moe_bursty_training",
|
| 104 |
+
"training_with_low_fabric_high_checkpoint",
|
| 105 |
+
"training_with_delayed_logs",
|
| 106 |
+
"multi_stage_training_pipeline",
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
REQUIRED_OUTPUTS = [
|
| 110 |
+
"README.md",
|
| 111 |
+
"manifest.json",
|
| 112 |
+
"model.joblib",
|
| 113 |
+
"preprocessing.joblib",
|
| 114 |
+
"feature_columns.json",
|
| 115 |
+
"excluded_columns.json",
|
| 116 |
+
"split_manifest.json",
|
| 117 |
+
"metrics.json",
|
| 118 |
+
"calibration_metrics.json",
|
| 119 |
+
"confusion_matrix.csv",
|
| 120 |
+
"classification_report.json",
|
| 121 |
+
"predictions_test.csv",
|
| 122 |
+
"predictions_all.csv",
|
| 123 |
+
"feature_importance.csv",
|
| 124 |
+
"evidence_audit_sample.csv",
|
| 125 |
+
"validation_summary.md",
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def predict_for_model_run(model_run_dir: Path, features_path: Path) -> tuple[pd.DataFrame, pd.DataFrame]:
|
| 130 |
+
df = load_feature_table(features_path)
|
| 131 |
+
split_manifest_path = model_run_dir / "split_manifest.json"
|
| 132 |
+
if split_manifest_path.exists():
|
| 133 |
+
df = apply_split_manifest(df, read_json(split_manifest_path))
|
| 134 |
+
else:
|
| 135 |
+
df = df.copy()
|
| 136 |
+
df["split"] = "unassigned"
|
| 137 |
+
|
| 138 |
+
feature_columns = read_json(model_run_dir / "feature_columns.json")
|
| 139 |
+
preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
|
| 140 |
+
model = joblib.load(model_run_dir / "model.joblib")
|
| 141 |
+
|
| 142 |
+
model_frame = model_input_frame(df, feature_columns)
|
| 143 |
+
transformed = preprocessor.transform(model_frame)
|
| 144 |
+
raw_probabilities = probability_frame(model, transformed)
|
| 145 |
+
governance = add_governance_outputs(df, raw_probabilities)
|
| 146 |
+
predictions = build_prediction_frame(df, raw_probabilities, governance)
|
| 147 |
+
return df, predictions
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def binary_prf(true_binary: pd.Series | np.ndarray, predicted_binary: pd.Series | np.ndarray) -> dict[str, float]:
|
| 151 |
+
precision, recall, f1, _ = precision_recall_fscore_support(
|
| 152 |
+
true_binary.astype(bool),
|
| 153 |
+
predicted_binary.astype(bool),
|
| 154 |
+
average="binary",
|
| 155 |
+
zero_division=0,
|
| 156 |
+
)
|
| 157 |
+
return {"precision": float(precision), "recall": float(recall), "f1": float(f1)}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def scenario_counts(frame: pd.DataFrame) -> dict[str, int]:
|
| 161 |
+
if len(frame) == 0:
|
| 162 |
+
return {}
|
| 163 |
+
return {str(key): int(value) for key, value in frame["latent_workload_class"].value_counts().sort_values(ascending=False).items()}
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def calibration_by_label(test_predictions: pd.DataFrame) -> dict[str, Any]:
|
| 167 |
+
y_true = test_predictions["label_0_to_4"].astype(int).to_numpy()
|
| 168 |
+
out: dict[str, Any] = {}
|
| 169 |
+
for label in LABELS:
|
| 170 |
+
observed = (y_true == label).astype(int)
|
| 171 |
+
predicted = test_predictions[f"p_label_{label}"].to_numpy()
|
| 172 |
+
out[str(label)] = {
|
| 173 |
+
"rows": int(len(test_predictions)),
|
| 174 |
+
"observed_fraction": float(observed.mean()) if len(observed) else 0.0,
|
| 175 |
+
"mean_predicted_probability": float(predicted.mean()) if len(predicted) else 0.0,
|
| 176 |
+
"brier": float(brier_score_loss(observed, predicted)) if len(np.unique(observed)) > 1 else float(np.mean((predicted - observed) ** 2)),
|
| 177 |
+
}
|
| 178 |
+
return out
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def reliability_bins(probabilities: np.ndarray, observed: np.ndarray, bin_count: int = 10) -> tuple[list[dict[str, Any]], float]:
|
| 182 |
+
bins: list[dict[str, Any]] = []
|
| 183 |
+
expected_calibration_error = 0.0
|
| 184 |
+
total = len(probabilities)
|
| 185 |
+
edges = np.linspace(0.0, 1.0, bin_count + 1)
|
| 186 |
+
for index in range(bin_count):
|
| 187 |
+
lower = edges[index]
|
| 188 |
+
upper = edges[index + 1]
|
| 189 |
+
if index == bin_count - 1:
|
| 190 |
+
mask = (probabilities >= lower) & (probabilities <= upper)
|
| 191 |
+
else:
|
| 192 |
+
mask = (probabilities >= lower) & (probabilities < upper)
|
| 193 |
+
count = int(mask.sum())
|
| 194 |
+
if count:
|
| 195 |
+
mean_predicted = float(probabilities[mask].mean())
|
| 196 |
+
observed_fraction = float(observed[mask].mean())
|
| 197 |
+
expected_calibration_error += (count / total) * abs(mean_predicted - observed_fraction)
|
| 198 |
+
else:
|
| 199 |
+
mean_predicted = 0.0
|
| 200 |
+
observed_fraction = 0.0
|
| 201 |
+
bins.append(
|
| 202 |
+
{
|
| 203 |
+
"bin_index": index,
|
| 204 |
+
"lower": float(lower),
|
| 205 |
+
"upper": float(upper),
|
| 206 |
+
"count": count,
|
| 207 |
+
"mean_predicted_probability": mean_predicted,
|
| 208 |
+
"observed_fraction": observed_fraction,
|
| 209 |
+
}
|
| 210 |
+
)
|
| 211 |
+
return bins, float(expected_calibration_error)
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def compute_calibration_metrics(test_predictions: pd.DataFrame) -> dict[str, Any]:
|
| 215 |
+
y_true = test_predictions["label_0_to_4"].astype(int).to_numpy()
|
| 216 |
+
probabilities = test_predictions[PROB_COLUMNS].to_numpy()
|
| 217 |
+
large_observed = (y_true >= 3).astype(int)
|
| 218 |
+
large_probability = test_predictions["p_large_training"].to_numpy()
|
| 219 |
+
bins, ece = reliability_bins(large_probability, large_observed)
|
| 220 |
+
return {
|
| 221 |
+
"log_loss": float(log_loss(y_true, probabilities, labels=LABELS)),
|
| 222 |
+
"brier_large_training": float(brier_score_loss(large_observed, large_probability)),
|
| 223 |
+
"expected_calibration_error_large_training": ece,
|
| 224 |
+
"reliability_bins_large_training": bins,
|
| 225 |
+
"calibration_by_label": calibration_by_label(test_predictions),
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def subgroup_metrics(test_predictions: pd.DataFrame, group_column: str) -> list[dict[str, Any]]:
|
| 230 |
+
if group_column not in test_predictions.columns:
|
| 231 |
+
return []
|
| 232 |
+
rows: list[dict[str, Any]] = []
|
| 233 |
+
for value, part in test_predictions.groupby(group_column, dropna=False):
|
| 234 |
+
y_true = part["label_0_to_4"].astype(int)
|
| 235 |
+
y_pred = part["predicted_label"].astype(int)
|
| 236 |
+
large_true = y_true >= 3
|
| 237 |
+
large_pred = part["p_large_training"] >= 0.5
|
| 238 |
+
prf = binary_prf(large_true.to_numpy(), large_pred.to_numpy())
|
| 239 |
+
rows.append(
|
| 240 |
+
{
|
| 241 |
+
"group": str(value),
|
| 242 |
+
"rows": int(len(part)),
|
| 243 |
+
"accuracy": float(accuracy_score(y_true, y_pred)),
|
| 244 |
+
"macro_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="macro", zero_division=0)),
|
| 245 |
+
"large_training_precision_at_0_5": prf["precision"],
|
| 246 |
+
"large_training_recall_at_0_5": prf["recall"],
|
| 247 |
+
"large_training_false_positives": int(((~large_true) & large_pred).sum()),
|
| 248 |
+
"large_training_false_negatives": int((large_true & (~large_pred)).sum()),
|
| 249 |
+
}
|
| 250 |
+
)
|
| 251 |
+
return sorted(rows, key=lambda item: (-item["rows"], item["group"]))
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def compute_metrics(test_predictions: pd.DataFrame, test_features: pd.DataFrame) -> tuple[dict[str, Any], dict[str, Any]]:
|
| 255 |
+
y_true = test_predictions["label_0_to_4"].astype(int)
|
| 256 |
+
y_pred = test_predictions["predicted_label"].astype(int)
|
| 257 |
+
probabilities = test_predictions[PROB_COLUMNS].to_numpy()
|
| 258 |
+
large_true = y_true >= 3
|
| 259 |
+
large_pred_by_label = y_pred >= 3
|
| 260 |
+
large_pred_by_threshold = test_predictions["p_large_training"] >= 0.5
|
| 261 |
+
large_label_prf = binary_prf(large_true.to_numpy(), large_pred_by_label.to_numpy())
|
| 262 |
+
large_threshold_prf = binary_prf(large_true.to_numpy(), large_pred_by_threshold.to_numpy())
|
| 263 |
+
|
| 264 |
+
y_one_hot = np.zeros_like(probabilities)
|
| 265 |
+
y_one_hot[np.arange(len(y_true)), y_true.to_numpy()] = 1.0
|
| 266 |
+
|
| 267 |
+
rule_labels = predict_rule_labels(test_features)
|
| 268 |
+
rule_large = rule_labels >= 3
|
| 269 |
+
rule_prf = binary_prf(large_true.to_numpy(), rule_large.to_numpy())
|
| 270 |
+
|
| 271 |
+
false_positives = test_predictions[(y_true < 3) & large_pred_by_threshold]
|
| 272 |
+
false_negatives = test_predictions[(y_true >= 3) & (~large_pred_by_threshold)]
|
| 273 |
+
high_coverage_label0 = test_predictions[
|
| 274 |
+
(y_true == 0) & (pd.to_numeric(test_predictions["min_critical_coverage"], errors="coerce").fillna(0.0) >= 0.95)
|
| 275 |
+
]
|
| 276 |
+
label0_missed = high_coverage_label0[high_coverage_label0["predicted_label"] != 0]
|
| 277 |
+
|
| 278 |
+
metrics: dict[str, Any] = {
|
| 279 |
+
"dataset": {
|
| 280 |
+
"test_rows": int(len(test_predictions)),
|
| 281 |
+
"test_episodes": int(test_predictions["episode_id"].nunique()),
|
| 282 |
+
"test_label_distribution": {
|
| 283 |
+
str(label): int(count) for label, count in y_true.value_counts().sort_index().items()
|
| 284 |
+
},
|
| 285 |
+
},
|
| 286 |
+
"model": {
|
| 287 |
+
"accuracy": float(accuracy_score(y_true, y_pred)),
|
| 288 |
+
"macro_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="macro", zero_division=0)),
|
| 289 |
+
"weighted_f1": float(f1_score(y_true, y_pred, labels=LABELS, average="weighted", zero_division=0)),
|
| 290 |
+
"log_loss": float(log_loss(y_true, probabilities, labels=LABELS)),
|
| 291 |
+
"brier_multiclass_mean": float(np.mean((probabilities - y_one_hot) ** 2)),
|
| 292 |
+
},
|
| 293 |
+
"governance": {
|
| 294 |
+
"label_3_4_predicted_label": large_label_prf,
|
| 295 |
+
"p_large_training_threshold_0_5": large_threshold_prf,
|
| 296 |
+
"false_positive_scenarios_at_0_5": scenario_counts(false_positives),
|
| 297 |
+
"false_negative_scenarios_at_0_5": scenario_counts(false_negatives),
|
| 298 |
+
"label_0_missed_under_high_coverage": {
|
| 299 |
+
"rows": int(len(high_coverage_label0)),
|
| 300 |
+
"missed_rows": int(len(label0_missed)),
|
| 301 |
+
"missed_rate": float(len(label0_missed) / len(high_coverage_label0)) if len(high_coverage_label0) else 0.0,
|
| 302 |
+
},
|
| 303 |
+
},
|
| 304 |
+
"rule_baseline": {
|
| 305 |
+
"accuracy": float(accuracy_score(y_true, rule_labels)),
|
| 306 |
+
"macro_f1": float(f1_score(y_true, rule_labels, labels=LABELS, average="macro", zero_division=0)),
|
| 307 |
+
"weighted_f1": float(f1_score(y_true, rule_labels, labels=LABELS, average="weighted", zero_division=0)),
|
| 308 |
+
"label_3_4_predicted_label": rule_prf,
|
| 309 |
+
},
|
| 310 |
+
"subgroup_metrics": {
|
| 311 |
+
column: subgroup_metrics(test_predictions, column)
|
| 312 |
+
for column in [
|
| 313 |
+
"latent_workload_class",
|
| 314 |
+
"scenario_family",
|
| 315 |
+
"data_quality_regime",
|
| 316 |
+
"temporal_phase",
|
| 317 |
+
"site_id",
|
| 318 |
+
"window_length_seconds",
|
| 319 |
+
"o4_missing_reason",
|
| 320 |
+
"o7_missing_reason",
|
| 321 |
+
"o12_missing_reason",
|
| 322 |
+
"capacity_possible",
|
| 323 |
+
"integrity_warning",
|
| 324 |
+
]
|
| 325 |
+
},
|
| 326 |
+
}
|
| 327 |
+
calibration = compute_calibration_metrics(test_predictions)
|
| 328 |
+
metrics["calibration"] = {
|
| 329 |
+
"log_loss": calibration["log_loss"],
|
| 330 |
+
"brier_large_training": calibration["brier_large_training"],
|
| 331 |
+
"expected_calibration_error_large_training": calibration["expected_calibration_error_large_training"],
|
| 332 |
+
}
|
| 333 |
+
return metrics, calibration
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def write_confusion_matrix(path: Path, test_predictions: pd.DataFrame) -> None:
|
| 337 |
+
y_true = test_predictions["label_0_to_4"].astype(int)
|
| 338 |
+
y_pred = test_predictions["predicted_label"].astype(int)
|
| 339 |
+
matrix = confusion_matrix(y_true, y_pred, labels=LABELS)
|
| 340 |
+
frame = pd.DataFrame(matrix, index=[f"true_{label}" for label in LABELS], columns=[f"pred_{label}" for label in LABELS])
|
| 341 |
+
frame.to_csv(path, index_label="true_label")
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def write_classification_report(path: Path, test_predictions: pd.DataFrame) -> None:
|
| 345 |
+
report = classification_report(
|
| 346 |
+
test_predictions["label_0_to_4"].astype(int),
|
| 347 |
+
test_predictions["predicted_label"].astype(int),
|
| 348 |
+
labels=LABELS,
|
| 349 |
+
output_dict=True,
|
| 350 |
+
zero_division=0,
|
| 351 |
+
)
|
| 352 |
+
write_json(path, report)
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def compute_feature_importance(
|
| 356 |
+
model_run_dir: Path,
|
| 357 |
+
test_features: pd.DataFrame,
|
| 358 |
+
test_predictions: pd.DataFrame,
|
| 359 |
+
seed: int = DEFAULT_SEED,
|
| 360 |
+
repeats: int = 3,
|
| 361 |
+
) -> pd.DataFrame:
|
| 362 |
+
feature_columns = read_json(model_run_dir / "feature_columns.json")
|
| 363 |
+
preprocessor = joblib.load(model_run_dir / "preprocessing.joblib")
|
| 364 |
+
model = joblib.load(model_run_dir / "model.joblib")
|
| 365 |
+
x_test = model_input_frame(test_features, feature_columns)
|
| 366 |
+
y_true = test_predictions["label_0_to_4"].astype(int)
|
| 367 |
+
baseline_pred = np.asarray(LABELS)[
|
| 368 |
+
np.argmax(probability_frame(model, preprocessor.transform(x_test))[PROB_COLUMNS].to_numpy(), axis=1)
|
| 369 |
+
]
|
| 370 |
+
baseline_score = f1_score(y_true, baseline_pred, labels=LABELS, average="macro", zero_division=0)
|
| 371 |
+
rng = np.random.default_rng(seed)
|
| 372 |
+
rows: list[dict[str, Any]] = []
|
| 373 |
+
for column in feature_columns:
|
| 374 |
+
scores: list[float] = []
|
| 375 |
+
if x_test[column].nunique(dropna=False) <= 1:
|
| 376 |
+
importances = [0.0] * repeats
|
| 377 |
+
else:
|
| 378 |
+
importances = []
|
| 379 |
+
for _ in range(repeats):
|
| 380 |
+
permuted = x_test.copy()
|
| 381 |
+
values = permuted[column].to_numpy(copy=True)
|
| 382 |
+
rng.shuffle(values)
|
| 383 |
+
permuted[column] = values
|
| 384 |
+
permuted_pred = np.asarray(LABELS)[
|
| 385 |
+
np.argmax(
|
| 386 |
+
probability_frame(model, preprocessor.transform(permuted))[PROB_COLUMNS].to_numpy(),
|
| 387 |
+
axis=1,
|
| 388 |
+
)
|
| 389 |
+
]
|
| 390 |
+
score = f1_score(y_true, permuted_pred, labels=LABELS, average="macro", zero_division=0)
|
| 391 |
+
scores.append(float(score))
|
| 392 |
+
importances.append(float(baseline_score - score))
|
| 393 |
+
rows.append(
|
| 394 |
+
{
|
| 395 |
+
"feature": column,
|
| 396 |
+
"importance_mean": float(np.mean(importances)),
|
| 397 |
+
"importance_std": float(np.std(importances)),
|
| 398 |
+
"baseline_macro_f1": float(baseline_score),
|
| 399 |
+
"permuted_macro_f1_mean": float(np.mean(scores)) if scores else float(baseline_score),
|
| 400 |
+
}
|
| 401 |
+
)
|
| 402 |
+
return pd.DataFrame(rows).sort_values(["importance_mean", "feature"], ascending=[False, True])
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
def evidence_audit_sample(test_predictions: pd.DataFrame, min_rows: int = 50) -> pd.DataFrame:
|
| 406 |
+
selected_indices: set[int] = set()
|
| 407 |
+
samples: list[pd.DataFrame] = []
|
| 408 |
+
|
| 409 |
+
def add_sample(name: str, mask: pd.Series, count: int, sort_column: str | None = None, ascending: bool = False) -> None:
|
| 410 |
+
candidates = test_predictions[mask & (~test_predictions.index.isin(selected_indices))].copy()
|
| 411 |
+
if sort_column and sort_column in candidates.columns:
|
| 412 |
+
candidates = candidates.sort_values(sort_column, ascending=ascending)
|
| 413 |
+
candidates = candidates.head(count)
|
| 414 |
+
if len(candidates):
|
| 415 |
+
selected_indices.update(int(index) for index in candidates.index)
|
| 416 |
+
candidates["audit_category"] = name
|
| 417 |
+
samples.append(candidates)
|
| 418 |
+
|
| 419 |
+
true_label = test_predictions["label_0_to_4"].astype(int)
|
| 420 |
+
predicted_label = test_predictions["predicted_label"].astype(int)
|
| 421 |
+
large_pred = test_predictions["p_large_training"] >= 0.5
|
| 422 |
+
add_sample("correct_label_0", (true_label == 0) & (predicted_label == 0), 10, "negative_certification_confidence", False)
|
| 423 |
+
add_sample(
|
| 424 |
+
"correct_label_2_hard_false_positive",
|
| 425 |
+
(true_label == 2)
|
| 426 |
+
& (predicted_label == 2)
|
| 427 |
+
& test_predictions["latent_workload_class"].isin(HARD_FALSE_POSITIVE_SCENARIOS),
|
| 428 |
+
10,
|
| 429 |
+
"p_large_training",
|
| 430 |
+
False,
|
| 431 |
+
)
|
| 432 |
+
add_sample("correct_label_3_4_likely_training", (true_label >= 3) & (predicted_label >= 3), 10, "p_large_training", False)
|
| 433 |
+
add_sample("false_positive_large_training", (true_label < 3) & large_pred, 10, "p_large_training", False)
|
| 434 |
+
add_sample("false_negative_large_training", (true_label >= 3) & (~large_pred), 10, "p_large_training", True)
|
| 435 |
+
|
| 436 |
+
if samples:
|
| 437 |
+
sample = pd.concat(samples, axis=0)
|
| 438 |
+
else:
|
| 439 |
+
sample = test_predictions.iloc[0:0].copy()
|
| 440 |
+
if len(sample) < min_rows:
|
| 441 |
+
filler = test_predictions[~test_predictions.index.isin(selected_indices)].copy()
|
| 442 |
+
filler["uncertainty_distance"] = (filler["p_large_training"] - 0.5).abs()
|
| 443 |
+
filler = filler.sort_values(["uncertainty_distance", "severity_score"], ascending=[True, False]).head(min_rows - len(sample))
|
| 444 |
+
filler["audit_category"] = "high_uncertainty_or_coverage_edge"
|
| 445 |
+
sample = pd.concat([sample, filler], axis=0)
|
| 446 |
+
|
| 447 |
+
columns = [
|
| 448 |
+
"audit_category",
|
| 449 |
+
"feature_row_id",
|
| 450 |
+
"split",
|
| 451 |
+
"latent_workload_class",
|
| 452 |
+
"label_0_to_4",
|
| 453 |
+
"predicted_label",
|
| 454 |
+
"p_large_training",
|
| 455 |
+
"severity_score",
|
| 456 |
+
"top_evidence",
|
| 457 |
+
"critical_missing_layers",
|
| 458 |
+
"integrity_warning",
|
| 459 |
+
]
|
| 460 |
+
columns += [column for column in SELECTED_AUDIT_FEATURES if column in sample.columns]
|
| 461 |
+
return sample.loc[:, [column for column in columns if column in sample.columns]].rename(columns={"label_0_to_4": "true_label"})
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def top_scenarios_text(scenarios: dict[str, int], limit: int = 5) -> str:
|
| 465 |
+
if not scenarios:
|
| 466 |
+
return "none"
|
| 467 |
+
return ", ".join(f"{name}: {count}" for name, count in list(scenarios.items())[:limit])
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def write_run_readme(model_run_dir: Path, features_path: Path, metrics: dict[str, Any], calibration: dict[str, Any]) -> None:
|
| 471 |
+
model_metrics = metrics["model"]
|
| 472 |
+
governance = metrics["governance"]
|
| 473 |
+
split_manifest = read_json(model_run_dir / "split_manifest.json")
|
| 474 |
+
dataset_dir = features_path.parent.parent if features_path.parent.name == "features" else features_path.parent
|
| 475 |
+
dataset_name = dataset_dir.name
|
| 476 |
+
model_run_name = model_run_dir.name
|
| 477 |
+
readme = f"""# {model_run_name} model run
|
| 478 |
+
|
| 479 |
+
This directory contains a public runnable baseline for the `{dataset_name}` datacenter training-run verification dataset.
|
| 480 |
+
|
| 481 |
+
## Dataset
|
| 482 |
+
|
| 483 |
+
- Feature table: `{features_path}`
|
| 484 |
+
- Rows: {sum(split['rows'] for split in split_manifest['summary'].values())}
|
| 485 |
+
- Episode split: grouped by `episode_id`, scenario-stratified, seed `{split_manifest['seed']}`
|
| 486 |
+
- Split rows: train {split_manifest['summary']['train']['rows']}, validation {split_manifest['summary']['validation']['rows']}, test {split_manifest['summary']['test']['rows']}
|
| 487 |
+
- Split episodes: train {split_manifest['summary']['train']['episodes']}, validation {split_manifest['summary']['validation']['episodes']}, test {split_manifest['summary']['test']['episodes']}
|
| 488 |
+
|
| 489 |
+
## Model
|
| 490 |
+
|
| 491 |
+
- Supervised model: calibrated scikit-learn histogram gradient boosting classifier
|
| 492 |
+
- Calibration: validation split only, held-out test evaluated once
|
| 493 |
+
- Rule baseline: deterministic evidence rules in `src/datacenter_verification_modeling/rule_baseline.py`
|
| 494 |
+
- Leakage exclusions: identifiers, labels, site id, episode id, raw manifest hash, scenario metadata, counterfactual metadata, and synthetic-only audit columns
|
| 495 |
+
|
| 496 |
+
## Headline Test Metrics
|
| 497 |
+
|
| 498 |
+
- Accuracy: {model_metrics['accuracy']:.4f}
|
| 499 |
+
- Macro F1: {model_metrics['macro_f1']:.4f}
|
| 500 |
+
- Weighted F1: {model_metrics['weighted_f1']:.4f}
|
| 501 |
+
- Log loss: {model_metrics['log_loss']:.4f}
|
| 502 |
+
- Label 3/4 precision by predicted label: {governance['label_3_4_predicted_label']['precision']:.4f}
|
| 503 |
+
- Label 3/4 recall by predicted label: {governance['label_3_4_predicted_label']['recall']:.4f}
|
| 504 |
+
- `p_large_training >= 0.5` precision: {governance['p_large_training_threshold_0_5']['precision']:.4f}
|
| 505 |
+
- `p_large_training >= 0.5` recall: {governance['p_large_training_threshold_0_5']['recall']:.4f}
|
| 506 |
+
- Rule baseline macro F1: {metrics['rule_baseline']['macro_f1']:.4f}
|
| 507 |
+
|
| 508 |
+
## Error Scenarios
|
| 509 |
+
|
| 510 |
+
- Largest false-positive scenarios at `p_large_training >= 0.5`: {top_scenarios_text(governance['false_positive_scenarios_at_0_5'])}
|
| 511 |
+
- Largest false-negative scenarios at `p_large_training >= 0.5`: {top_scenarios_text(governance['false_negative_scenarios_at_0_5'])}
|
| 512 |
+
|
| 513 |
+
## Calibration
|
| 514 |
+
|
| 515 |
+
- Brier score for `p_large_training`: {calibration['brier_large_training']:.4f}
|
| 516 |
+
- Expected calibration error for `p_large_training`: {calibration['expected_calibration_error_large_training']:.4f}
|
| 517 |
+
|
| 518 |
+
## Reproduce
|
| 519 |
+
|
| 520 |
+
```bash
|
| 521 |
+
python src/datacenter_verification_modeling/train_model.py \\
|
| 522 |
+
--features {features_path} \\
|
| 523 |
+
--output {model_run_dir} \\
|
| 524 |
+
--seed {split_manifest['seed']}
|
| 525 |
+
```
|
| 526 |
+
|
| 527 |
+
```bash
|
| 528 |
+
python src/datacenter_verification_modeling/evaluate_model.py \\
|
| 529 |
+
--model-run {model_run_dir} \\
|
| 530 |
+
--features {features_path}
|
| 531 |
+
```
|
| 532 |
+
|
| 533 |
+
```bash
|
| 534 |
+
python src/datacenter_verification_modeling/predict.py \\
|
| 535 |
+
--model-run {model_run_dir} \\
|
| 536 |
+
--features {features_path} \\
|
| 537 |
+
--output {model_run_dir / 'predictions_all.csv'}
|
| 538 |
+
```
|
| 539 |
+
|
| 540 |
+
## Limitations
|
| 541 |
+
|
| 542 |
+
- This model is trained on synthetic data only.
|
| 543 |
+
- Performance numbers are not real-world deployment claims.
|
| 544 |
+
- Adjacent windows are correlated, so group splitting by `episode_id` is mandatory.
|
| 545 |
+
- Synthetic labels are generated from rules and latent scenarios, so the model may learn generator assumptions.
|
| 546 |
+
- Real datacenter deployment would require calibration on real telemetry and controlled drills.
|
| 547 |
+
- The model should assist audit triage; it should not be treated as sole proof of a violation.
|
| 548 |
+
"""
|
| 549 |
+
(model_run_dir / "README.md").write_text(readme, encoding="utf-8")
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
def write_validation_summary(
|
| 553 |
+
model_run_dir: Path,
|
| 554 |
+
features_path: Path,
|
| 555 |
+
metrics: dict[str, Any],
|
| 556 |
+
validation_status: list[dict[str, Any]] | None,
|
| 557 |
+
) -> None:
|
| 558 |
+
if validation_status is None and (model_run_dir / "manifest.json").exists():
|
| 559 |
+
existing_manifest = read_json(model_run_dir / "manifest.json")
|
| 560 |
+
validation_status = existing_manifest.get("validation_status") or None
|
| 561 |
+
required_rows = []
|
| 562 |
+
for filename in REQUIRED_OUTPUTS:
|
| 563 |
+
path = model_run_dir / filename
|
| 564 |
+
exists = path.exists() or filename == "validation_summary.md"
|
| 565 |
+
required_rows.append(f"- `{filename}`: {'present' if exists else 'missing'}")
|
| 566 |
+
validation_lines = []
|
| 567 |
+
if validation_status:
|
| 568 |
+
for item in validation_status:
|
| 569 |
+
validation_lines.append(
|
| 570 |
+
f"- `{item['name']}`: return code {item['returncode']}; "
|
| 571 |
+
f"{'PASS' if item['returncode'] == 0 else 'FAIL'}"
|
| 572 |
+
)
|
| 573 |
+
else:
|
| 574 |
+
validation_lines.append("- Dataset validation was not rerun by this evaluation command; see `manifest.json` for any recorded training validation status.")
|
| 575 |
+
|
| 576 |
+
model_metrics = metrics["model"]
|
| 577 |
+
governance = metrics["governance"]
|
| 578 |
+
text = f"""# Validation Summary
|
| 579 |
+
|
| 580 |
+
Feature table: `{features_path}`
|
| 581 |
+
|
| 582 |
+
## Dataset Validation Before Training
|
| 583 |
+
|
| 584 |
+
{chr(10).join(validation_lines)}
|
| 585 |
+
|
| 586 |
+
## Required Artifacts
|
| 587 |
+
|
| 588 |
+
{chr(10).join(required_rows)}
|
| 589 |
+
|
| 590 |
+
## Test Metrics
|
| 591 |
+
|
| 592 |
+
- Accuracy: {model_metrics['accuracy']:.4f}
|
| 593 |
+
- Macro F1: {model_metrics['macro_f1']:.4f}
|
| 594 |
+
- Label 3/4 precision: {governance['label_3_4_predicted_label']['precision']:.4f}
|
| 595 |
+
- Label 3/4 recall: {governance['label_3_4_predicted_label']['recall']:.4f}
|
| 596 |
+
- `p_large_training >= 0.5` precision: {governance['p_large_training_threshold_0_5']['precision']:.4f}
|
| 597 |
+
- `p_large_training >= 0.5` recall: {governance['p_large_training_threshold_0_5']['recall']:.4f}
|
| 598 |
+
|
| 599 |
+
## Governance Checks
|
| 600 |
+
|
| 601 |
+
- Capacity gate applied to post-processed probabilities.
|
| 602 |
+
- Negative certification confidence is `p_label_0 * min_critical_coverage`.
|
| 603 |
+
- Integrity warnings are reported separately from positive training evidence.
|
| 604 |
+
- Raw model probabilities are retained as `raw_p_label_0` through `raw_p_label_4`.
|
| 605 |
+
"""
|
| 606 |
+
(model_run_dir / "validation_summary.md").write_text(text, encoding="utf-8")
|
| 607 |
+
|
| 608 |
+
|
| 609 |
+
def write_manifest(
|
| 610 |
+
model_run_dir: Path,
|
| 611 |
+
features_path: Path,
|
| 612 |
+
metrics: dict[str, Any],
|
| 613 |
+
calibration: dict[str, Any],
|
| 614 |
+
validation_status: list[dict[str, Any]] | None,
|
| 615 |
+
training_metadata: dict[str, Any] | None,
|
| 616 |
+
) -> None:
|
| 617 |
+
existing: dict[str, Any] = {}
|
| 618 |
+
manifest_path = model_run_dir / "manifest.json"
|
| 619 |
+
if manifest_path.exists():
|
| 620 |
+
existing = read_json(manifest_path)
|
| 621 |
+
artifact_hashes = {
|
| 622 |
+
filename: sha256_file(model_run_dir / filename)
|
| 623 |
+
for filename in REQUIRED_OUTPUTS
|
| 624 |
+
if (model_run_dir / filename).exists() and filename != "manifest.json"
|
| 625 |
+
}
|
| 626 |
+
manifest = {
|
| 627 |
+
**existing,
|
| 628 |
+
"created_or_updated_at": utc_now_iso(),
|
| 629 |
+
"model_run_id": model_run_dir.name,
|
| 630 |
+
"features_path": str(features_path),
|
| 631 |
+
"model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
|
| 632 |
+
"calibration_method": existing.get("calibration_method", "sigmoid_on_validation_split"),
|
| 633 |
+
"metrics_summary": {
|
| 634 |
+
"accuracy": metrics["model"]["accuracy"],
|
| 635 |
+
"macro_f1": metrics["model"]["macro_f1"],
|
| 636 |
+
"weighted_f1": metrics["model"]["weighted_f1"],
|
| 637 |
+
"label_3_4_precision": metrics["governance"]["label_3_4_predicted_label"]["precision"],
|
| 638 |
+
"label_3_4_recall": metrics["governance"]["label_3_4_predicted_label"]["recall"],
|
| 639 |
+
"p_large_training_precision_at_0_5": metrics["governance"]["p_large_training_threshold_0_5"]["precision"],
|
| 640 |
+
"p_large_training_recall_at_0_5": metrics["governance"]["p_large_training_threshold_0_5"]["recall"],
|
| 641 |
+
"brier_large_training": calibration["brier_large_training"],
|
| 642 |
+
"ece_large_training": calibration["expected_calibration_error_large_training"],
|
| 643 |
+
},
|
| 644 |
+
"validation_status": validation_status or existing.get("validation_status", []),
|
| 645 |
+
"training_metadata": training_metadata or existing.get("training_metadata", {}),
|
| 646 |
+
"required_outputs": REQUIRED_OUTPUTS,
|
| 647 |
+
"artifact_hashes": artifact_hashes,
|
| 648 |
+
"limitations": [
|
| 649 |
+
"Trained on synthetic data only.",
|
| 650 |
+
"Performance numbers are not real-world deployment claims.",
|
| 651 |
+
"Episode-level group splitting is required because adjacent windows are correlated.",
|
| 652 |
+
"Synthetic labels encode generator assumptions.",
|
| 653 |
+
"Real deployment requires real telemetry calibration and controlled drills.",
|
| 654 |
+
],
|
| 655 |
+
}
|
| 656 |
+
write_json(manifest_path, manifest)
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
def evaluate_model_run(
|
| 660 |
+
model_run_dir: Path,
|
| 661 |
+
features_path: Path,
|
| 662 |
+
validation_status: list[dict[str, Any]] | None = None,
|
| 663 |
+
training_metadata: dict[str, Any] | None = None,
|
| 664 |
+
) -> dict[str, Any]:
|
| 665 |
+
model_run_dir.mkdir(parents=True, exist_ok=True)
|
| 666 |
+
feature_df, predictions = predict_for_model_run(model_run_dir, features_path)
|
| 667 |
+
predictions.to_csv(model_run_dir / "predictions_all.csv", index=False)
|
| 668 |
+
test_predictions = predictions[predictions["split"] == "test"].copy()
|
| 669 |
+
test_features = feature_df[feature_df["split"] == "test"].copy()
|
| 670 |
+
if test_predictions.empty:
|
| 671 |
+
raise ValueError("no test split rows available for evaluation")
|
| 672 |
+
test_predictions.to_csv(model_run_dir / "predictions_test.csv", index=False)
|
| 673 |
+
|
| 674 |
+
metrics, calibration = compute_metrics(test_predictions, test_features)
|
| 675 |
+
write_json(model_run_dir / "metrics.json", metrics)
|
| 676 |
+
write_json(model_run_dir / "calibration_metrics.json", calibration)
|
| 677 |
+
write_confusion_matrix(model_run_dir / "confusion_matrix.csv", test_predictions)
|
| 678 |
+
write_classification_report(model_run_dir / "classification_report.json", test_predictions)
|
| 679 |
+
importance = compute_feature_importance(model_run_dir, test_features, test_predictions)
|
| 680 |
+
importance.to_csv(model_run_dir / "feature_importance.csv", index=False)
|
| 681 |
+
audit = evidence_audit_sample(test_predictions)
|
| 682 |
+
audit.to_csv(model_run_dir / "evidence_audit_sample.csv", index=False)
|
| 683 |
+
write_run_readme(model_run_dir, features_path, metrics, calibration)
|
| 684 |
+
write_manifest(model_run_dir, features_path, metrics, calibration, validation_status, training_metadata)
|
| 685 |
+
write_validation_summary(model_run_dir, features_path, metrics, validation_status)
|
| 686 |
+
write_manifest(model_run_dir, features_path, metrics, calibration, validation_status, training_metadata)
|
| 687 |
+
return metrics
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
def main(argv: list[str] | None = None) -> int:
|
| 691 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 692 |
+
parser.add_argument("--model-run", type=Path, required=True)
|
| 693 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 694 |
+
args = parser.parse_args(argv)
|
| 695 |
+
metrics = evaluate_model_run(args.model_run, args.features)
|
| 696 |
+
print(f"accuracy: {metrics['model']['accuracy']:.4f}")
|
| 697 |
+
print(f"macro_f1: {metrics['model']['macro_f1']:.4f}")
|
| 698 |
+
print(f"label_3_4_precision: {metrics['governance']['label_3_4_predicted_label']['precision']:.4f}")
|
| 699 |
+
print(f"label_3_4_recall: {metrics['governance']['label_3_4_predicted_label']['recall']:.4f}")
|
| 700 |
+
return 0
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
if __name__ == "__main__":
|
| 704 |
+
raise SystemExit(main())
|
src/datacenter_verification_modeling/evaluate_stress_splits.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate stress splits for synthetic datacenter verification datasets."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from collections import Counter
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import pandas as pd
|
| 12 |
+
from sklearn.ensemble import HistGradientBoostingClassifier
|
| 13 |
+
from sklearn.metrics import accuracy_score, f1_score, log_loss, precision_recall_fscore_support
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from .common import (
|
| 17 |
+
DEFAULT_SEED,
|
| 18 |
+
LABELS,
|
| 19 |
+
PROB_COLUMNS,
|
| 20 |
+
determine_feature_columns,
|
| 21 |
+
ensure_dir,
|
| 22 |
+
load_feature_table,
|
| 23 |
+
make_episode_split,
|
| 24 |
+
make_preprocessor,
|
| 25 |
+
model_input_frame,
|
| 26 |
+
probability_frame,
|
| 27 |
+
write_json,
|
| 28 |
+
)
|
| 29 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 30 |
+
from common import (
|
| 31 |
+
DEFAULT_SEED,
|
| 32 |
+
LABELS,
|
| 33 |
+
PROB_COLUMNS,
|
| 34 |
+
determine_feature_columns,
|
| 35 |
+
ensure_dir,
|
| 36 |
+
load_feature_table,
|
| 37 |
+
make_episode_split,
|
| 38 |
+
make_preprocessor,
|
| 39 |
+
model_input_frame,
|
| 40 |
+
probability_frame,
|
| 41 |
+
write_json,
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
FAMILY_HOLDOUTS = [
|
| 46 |
+
"underclocked_energy_capped_training",
|
| 47 |
+
"fragmented_training_linked",
|
| 48 |
+
"multi_tenant_fragmented_nontraining",
|
| 49 |
+
"model_parallel_inference",
|
| 50 |
+
"hpc_mpi_collective",
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
SOURCE_ABLATIONS = {
|
| 54 |
+
"source_ablation_drop_fabric": ["o6_", "o7_", "fabric_telemetry_trust_level"],
|
| 55 |
+
"source_ablation_drop_runtime_and_ml_logs": ["o10_", "o12_"],
|
| 56 |
+
"source_ablation_drop_gpu_telemetry": ["o4_", "o5_", "gpu_telemetry_trust_level"],
|
| 57 |
+
"source_ablation_drop_power": ["o8_", "o9_", "power_meter_trust_level"],
|
| 58 |
+
"source_ablation_drop_storage": ["o11_"],
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _base_classifier(seed: int, max_iter: int) -> HistGradientBoostingClassifier:
|
| 63 |
+
kwargs: dict[str, Any] = {
|
| 64 |
+
"learning_rate": 0.06,
|
| 65 |
+
"max_iter": max_iter,
|
| 66 |
+
"max_leaf_nodes": 31,
|
| 67 |
+
"l2_regularization": 0.04,
|
| 68 |
+
"early_stopping": True,
|
| 69 |
+
"validation_fraction": 0.15,
|
| 70 |
+
"random_state": seed,
|
| 71 |
+
}
|
| 72 |
+
try:
|
| 73 |
+
return HistGradientBoostingClassifier(class_weight="balanced", **kwargs)
|
| 74 |
+
except TypeError: # pragma: no cover
|
| 75 |
+
return HistGradientBoostingClassifier(**kwargs)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _binary_prf(y_true: np.ndarray, p_large: np.ndarray) -> dict[str, float]:
|
| 79 |
+
precision, recall, f1, _ = precision_recall_fscore_support(
|
| 80 |
+
y_true >= 3,
|
| 81 |
+
p_large >= 0.5,
|
| 82 |
+
average="binary",
|
| 83 |
+
zero_division=0,
|
| 84 |
+
)
|
| 85 |
+
return {"precision": float(precision), "recall": float(recall), "f1": float(f1)}
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _scenario_counts(df: pd.DataFrame, mask: pd.Series, scenario_column: str) -> dict[str, int]:
|
| 89 |
+
if not mask.any():
|
| 90 |
+
return {}
|
| 91 |
+
return {str(key): int(value) for key, value in df.loc[mask, scenario_column].value_counts().sort_values(ascending=False).items()}
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _middle_probability_bins(p_large: np.ndarray) -> dict[str, int]:
|
| 95 |
+
return {
|
| 96 |
+
"0.1_to_0.3": int(((p_large >= 0.1) & (p_large < 0.3)).sum()),
|
| 97 |
+
"0.3_to_0.7": int(((p_large >= 0.3) & (p_large < 0.7)).sum()),
|
| 98 |
+
"0.7_to_0.9": int(((p_large >= 0.7) & (p_large < 0.9)).sum()),
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _fit_predict_split(
|
| 103 |
+
df: pd.DataFrame,
|
| 104 |
+
train_mask: pd.Series,
|
| 105 |
+
test_mask: pd.Series,
|
| 106 |
+
feature_columns: list[str],
|
| 107 |
+
seed: int,
|
| 108 |
+
max_iter: int,
|
| 109 |
+
) -> tuple[pd.DataFrame, dict[str, Any]]:
|
| 110 |
+
train_df = df[train_mask].copy()
|
| 111 |
+
test_df = df[test_mask].copy()
|
| 112 |
+
if train_df.empty or test_df.empty:
|
| 113 |
+
raise ValueError("stress split has empty train or test set")
|
| 114 |
+
if train_df["label_0_to_4"].nunique() < 2:
|
| 115 |
+
raise ValueError("stress split train set has fewer than two labels")
|
| 116 |
+
|
| 117 |
+
preprocessor = make_preprocessor(train_df, feature_columns)
|
| 118 |
+
x_train = preprocessor.fit_transform(model_input_frame(train_df, feature_columns))
|
| 119 |
+
y_train = train_df["label_0_to_4"].astype(int).to_numpy()
|
| 120 |
+
x_test = preprocessor.transform(model_input_frame(test_df, feature_columns))
|
| 121 |
+
y_test = test_df["label_0_to_4"].astype(int).to_numpy()
|
| 122 |
+
|
| 123 |
+
model = _base_classifier(seed, max_iter)
|
| 124 |
+
model.fit(x_train, y_train)
|
| 125 |
+
probabilities = probability_frame(model, x_test)
|
| 126 |
+
p_values = probabilities[PROB_COLUMNS].to_numpy()
|
| 127 |
+
y_pred = np.asarray(LABELS)[np.argmax(p_values, axis=1)]
|
| 128 |
+
p_large = probabilities["p_label_3"].to_numpy() + probabilities["p_label_4"].to_numpy()
|
| 129 |
+
out = test_df[["feature_row_id", "episode_id", "label_0_to_4"]].copy()
|
| 130 |
+
for column in ["scenario_family", "latent_workload_class", "site_id", "window_start"]:
|
| 131 |
+
if column in test_df.columns:
|
| 132 |
+
out[column] = test_df[column].to_numpy()
|
| 133 |
+
out["predicted_label"] = y_pred
|
| 134 |
+
out["p_large_training"] = p_large
|
| 135 |
+
for column in PROB_COLUMNS:
|
| 136 |
+
out[column] = probabilities[column].to_numpy()
|
| 137 |
+
|
| 138 |
+
large_true = y_test >= 3
|
| 139 |
+
large_pred = p_large >= 0.5
|
| 140 |
+
scenario_column = "scenario_family" if "scenario_family" in test_df.columns else "latent_workload_class"
|
| 141 |
+
metrics = {
|
| 142 |
+
"train_rows": int(len(train_df)),
|
| 143 |
+
"test_rows": int(len(test_df)),
|
| 144 |
+
"train_episodes": int(train_df["episode_id"].nunique()),
|
| 145 |
+
"test_episodes": int(test_df["episode_id"].nunique()),
|
| 146 |
+
"test_label_distribution": {str(k): int(v) for k, v in Counter(y_test).items()},
|
| 147 |
+
"accuracy": float(accuracy_score(y_test, y_pred)),
|
| 148 |
+
"macro_f1": float(f1_score(y_test, y_pred, labels=LABELS, average="macro", zero_division=0)),
|
| 149 |
+
"large_training": _binary_prf(y_test, p_large),
|
| 150 |
+
"large_training_false_positive_count": int(((~large_true) & large_pred).sum()),
|
| 151 |
+
"large_training_false_negative_count": int((large_true & (~large_pred)).sum()),
|
| 152 |
+
"false_positive_scenarios": _scenario_counts(test_df, pd.Series((~large_true) & large_pred, index=test_df.index), scenario_column),
|
| 153 |
+
"false_negative_scenarios": _scenario_counts(test_df, pd.Series(large_true & (~large_pred), index=test_df.index), scenario_column),
|
| 154 |
+
"middle_probability_bins": _middle_probability_bins(p_large),
|
| 155 |
+
}
|
| 156 |
+
try:
|
| 157 |
+
metrics["log_loss"] = float(log_loss(y_test, p_values, labels=LABELS))
|
| 158 |
+
except ValueError:
|
| 159 |
+
metrics["log_loss"] = None
|
| 160 |
+
return out, metrics
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def _drop_features(feature_columns: list[str], prefixes: list[str]) -> list[str]:
|
| 164 |
+
dropped = []
|
| 165 |
+
for column in feature_columns:
|
| 166 |
+
if any(column == prefix or column.startswith(prefix) for prefix in prefixes):
|
| 167 |
+
continue
|
| 168 |
+
dropped.append(column)
|
| 169 |
+
return dropped
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def evaluate_stress_splits(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED, max_iter: int = 120) -> dict[str, Any]:
|
| 173 |
+
ensure_dir(output_dir)
|
| 174 |
+
df = load_feature_table(features_path)
|
| 175 |
+
scenario_column = "scenario_family" if "scenario_family" in df.columns else "latent_workload_class"
|
| 176 |
+
feature_columns, exclusion_meta = determine_feature_columns(df.copy())
|
| 177 |
+
metrics: dict[str, Any] = {
|
| 178 |
+
"features_path": str(features_path),
|
| 179 |
+
"seed": int(seed),
|
| 180 |
+
"max_iter": int(max_iter),
|
| 181 |
+
"scenario_column": scenario_column,
|
| 182 |
+
"model_feature_count": int(len(feature_columns)),
|
| 183 |
+
"exclusion_metadata": exclusion_meta,
|
| 184 |
+
"splits": {},
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
split_df, split_manifest = make_episode_split(df, seed=seed)
|
| 188 |
+
random_train = split_df["split"].isin(["train", "validation"])
|
| 189 |
+
random_test = split_df["split"] == "test"
|
| 190 |
+
_, metrics["splits"]["episode_grouped_random"] = _fit_predict_split(
|
| 191 |
+
split_df, random_train, random_test, feature_columns, seed, max_iter
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
sorted_times = pd.to_datetime(df["window_start"], utc=True)
|
| 195 |
+
cutoff = sorted_times.quantile(0.80)
|
| 196 |
+
_, metrics["splits"]["time_holdout"] = _fit_predict_split(
|
| 197 |
+
df, sorted_times < cutoff, sorted_times >= cutoff, feature_columns, seed + 1, max_iter
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
site_results = {}
|
| 201 |
+
for idx, site_id in enumerate(sorted(df["site_id"].dropna().unique())):
|
| 202 |
+
test_mask = df["site_id"] == site_id
|
| 203 |
+
train_mask = ~test_mask
|
| 204 |
+
try:
|
| 205 |
+
_, site_results[str(site_id)] = _fit_predict_split(df, train_mask, test_mask, feature_columns, seed + 10 + idx, max_iter)
|
| 206 |
+
except ValueError as exc:
|
| 207 |
+
site_results[str(site_id)] = {"error": str(exc)}
|
| 208 |
+
metrics["splits"]["site_holdout"] = site_results
|
| 209 |
+
|
| 210 |
+
family_results = {}
|
| 211 |
+
for idx, family in enumerate(FAMILY_HOLDOUTS):
|
| 212 |
+
if family not in set(df[scenario_column].astype(str)):
|
| 213 |
+
family_results[family] = {"error": "family not present"}
|
| 214 |
+
continue
|
| 215 |
+
test_mask = df[scenario_column].astype(str) == family
|
| 216 |
+
train_mask = ~test_mask
|
| 217 |
+
try:
|
| 218 |
+
_, family_results[family] = _fit_predict_split(df, train_mask, test_mask, feature_columns, seed + 30 + idx, max_iter)
|
| 219 |
+
except ValueError as exc:
|
| 220 |
+
family_results[family] = {"error": str(exc)}
|
| 221 |
+
metrics["splits"]["scenario_family_holdout"] = family_results
|
| 222 |
+
|
| 223 |
+
ablation_results = {}
|
| 224 |
+
for idx, (name, prefixes) in enumerate(SOURCE_ABLATIONS.items()):
|
| 225 |
+
ablated_features = _drop_features(feature_columns, prefixes)
|
| 226 |
+
try:
|
| 227 |
+
_, ablation_results[name] = _fit_predict_split(
|
| 228 |
+
split_df, random_train, random_test, ablated_features, seed + 50 + idx, max_iter
|
| 229 |
+
)
|
| 230 |
+
ablation_results[name]["feature_count"] = int(len(ablated_features))
|
| 231 |
+
ablation_results[name]["dropped_prefixes"] = prefixes
|
| 232 |
+
except ValueError as exc:
|
| 233 |
+
ablation_results[name] = {"error": str(exc)}
|
| 234 |
+
metrics["splits"]["source_ablations"] = ablation_results
|
| 235 |
+
|
| 236 |
+
write_json(output_dir / "stress_metrics.json", metrics)
|
| 237 |
+
write_stress_summary(output_dir / "stress_summary.md", metrics)
|
| 238 |
+
update_model_run_readme_with_stress(output_dir, metrics)
|
| 239 |
+
return metrics
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def _split_line(name: str, item: dict[str, Any]) -> str:
|
| 243 |
+
if "error" in item:
|
| 244 |
+
return f"- {name}: ERROR: {item['error']}"
|
| 245 |
+
return (
|
| 246 |
+
f"- {name}: rows={item['test_rows']}, macro F1={item['macro_f1']:.3f}, "
|
| 247 |
+
f"large precision={item['large_training']['precision']:.3f}, "
|
| 248 |
+
f"large recall={item['large_training']['recall']:.3f}, "
|
| 249 |
+
f"FP={item['large_training_false_positive_count']}, FN={item['large_training_false_negative_count']}, "
|
| 250 |
+
f"middle p bins={item['middle_probability_bins']}"
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def write_stress_summary(path: Path, metrics: dict[str, Any]) -> None:
|
| 255 |
+
splits = metrics["splits"]
|
| 256 |
+
lines = [
|
| 257 |
+
"# Synthetic v1 Stress Summary",
|
| 258 |
+
"",
|
| 259 |
+
f"Feature table: `{metrics['features_path']}`",
|
| 260 |
+
f"Seed: `{metrics['seed']}`",
|
| 261 |
+
f"Lightweight stress model max_iter: `{metrics['max_iter']}`",
|
| 262 |
+
"",
|
| 263 |
+
"## Core Splits",
|
| 264 |
+
"",
|
| 265 |
+
_split_line("episode_grouped_random", splits["episode_grouped_random"]),
|
| 266 |
+
_split_line("time_holdout", splits["time_holdout"]),
|
| 267 |
+
"",
|
| 268 |
+
"## Site Holdout",
|
| 269 |
+
"",
|
| 270 |
+
]
|
| 271 |
+
for site_id, item in splits["site_holdout"].items():
|
| 272 |
+
lines.append(_split_line(site_id, item))
|
| 273 |
+
lines.extend(["", "## Scenario Family Holdout", ""])
|
| 274 |
+
for family, item in splits["scenario_family_holdout"].items():
|
| 275 |
+
lines.append(_split_line(family, item))
|
| 276 |
+
if "false_negative_scenarios" in item and item["false_negative_scenarios"]:
|
| 277 |
+
lines.append(f" false negatives: {item['false_negative_scenarios']}")
|
| 278 |
+
if "false_positive_scenarios" in item and item["false_positive_scenarios"]:
|
| 279 |
+
lines.append(f" false positives: {item['false_positive_scenarios']}")
|
| 280 |
+
lines.extend(["", "## Source Ablations", ""])
|
| 281 |
+
baseline = splits["episode_grouped_random"]
|
| 282 |
+
baseline_f1 = baseline.get("macro_f1") if "error" not in baseline else None
|
| 283 |
+
for name, item in splits["source_ablations"].items():
|
| 284 |
+
lines.append(_split_line(name, item))
|
| 285 |
+
if baseline_f1 is not None and "macro_f1" in item:
|
| 286 |
+
lines.append(f" macro F1 delta vs random baseline: {item['macro_f1'] - baseline_f1:+.3f}")
|
| 287 |
+
lines.extend(
|
| 288 |
+
[
|
| 289 |
+
"",
|
| 290 |
+
"## Interpretation",
|
| 291 |
+
"",
|
| 292 |
+
"- These are stress diagnostics, not tuned headline metrics.",
|
| 293 |
+
"- Family holdouts identify hard families that still fail under scenario generalization.",
|
| 294 |
+
"- Source ablations should degrade performance when the dropped layer carries independent evidence.",
|
| 295 |
+
"- Middle probability bins indicate whether v1 has less all-or-nothing large-training probability mass than v0.",
|
| 296 |
+
]
|
| 297 |
+
)
|
| 298 |
+
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def update_model_run_readme_with_stress(model_run_dir: Path, metrics: dict[str, Any]) -> None:
|
| 302 |
+
readme_path = model_run_dir / "README.md"
|
| 303 |
+
if not readme_path.exists():
|
| 304 |
+
return
|
| 305 |
+
splits = metrics["splits"]
|
| 306 |
+
baseline = splits["episode_grouped_random"]
|
| 307 |
+
fragmented = splits["scenario_family_holdout"].get("fragmented_training_linked", {})
|
| 308 |
+
underclocked = splits["scenario_family_holdout"].get("underclocked_energy_capped_training", {})
|
| 309 |
+
runtime_drop = splits["source_ablations"].get("source_ablation_drop_runtime_and_ml_logs", {})
|
| 310 |
+
baseline_f1 = baseline.get("macro_f1", 0.0)
|
| 311 |
+
runtime_delta = runtime_drop.get("macro_f1", baseline_f1) - baseline_f1 if "macro_f1" in runtime_drop else 0.0
|
| 312 |
+
section = f"""## Stress Splits
|
| 313 |
+
|
| 314 |
+
Stress outputs are in:
|
| 315 |
+
|
| 316 |
+
- `stress_metrics.json`
|
| 317 |
+
- `stress_summary.md`
|
| 318 |
+
|
| 319 |
+
Key stress checks from the lightweight retraining script:
|
| 320 |
+
|
| 321 |
+
- Episode-grouped random split macro F1: {baseline.get('macro_f1', 0.0):.4f}; large-training recall: {baseline.get('large_training', {}).get('recall', 0.0):.4f}
|
| 322 |
+
- Time holdout macro F1: {splits['time_holdout'].get('macro_f1', 0.0):.4f}; large-training recall: {splits['time_holdout'].get('large_training', {}).get('recall', 0.0):.4f}
|
| 323 |
+
- Underclocked holdout large-training recall: {underclocked.get('large_training', {}).get('recall', 0.0):.4f}; false negatives: {underclocked.get('large_training_false_negative_count', 0)}
|
| 324 |
+
- Fragmented linked training holdout remains hard: {fragmented.get('large_training_false_negative_count', 0)} large-training false negatives
|
| 325 |
+
- Source ablation dropping runtime and ML logs changed macro F1 by {runtime_delta:+.3f} versus the random stress baseline
|
| 326 |
+
- Middle `p_large_training` bins are populated in stress checks: {baseline.get('middle_probability_bins', {})}
|
| 327 |
+
|
| 328 |
+
"""
|
| 329 |
+
readme = readme_path.read_text(encoding="utf-8")
|
| 330 |
+
start = readme.find("## Stress Splits\n")
|
| 331 |
+
if start != -1:
|
| 332 |
+
end = readme.find("## Reproduce\n", start)
|
| 333 |
+
if end == -1:
|
| 334 |
+
end = readme.find("## Limitations\n", start)
|
| 335 |
+
if end == -1:
|
| 336 |
+
readme = readme[:start].rstrip() + "\n\n" + section
|
| 337 |
+
else:
|
| 338 |
+
readme = readme[:start].rstrip() + "\n\n" + section + readme[end:]
|
| 339 |
+
else:
|
| 340 |
+
marker = "## Reproduce\n"
|
| 341 |
+
idx = readme.find(marker)
|
| 342 |
+
if idx == -1:
|
| 343 |
+
readme = readme.rstrip() + "\n\n" + section
|
| 344 |
+
else:
|
| 345 |
+
readme = readme[:idx].rstrip() + "\n\n" + section + readme[idx:]
|
| 346 |
+
readme_path.write_text(readme, encoding="utf-8")
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def main(argv: list[str] | None = None) -> int:
|
| 350 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 351 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 352 |
+
parser.add_argument("--model-run", type=Path, required=True, help="Directory to write stress outputs into.")
|
| 353 |
+
parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
|
| 354 |
+
parser.add_argument("--max-iter", type=int, default=120)
|
| 355 |
+
args = parser.parse_args(argv)
|
| 356 |
+
metrics = evaluate_stress_splits(args.features, args.model_run, seed=args.seed, max_iter=args.max_iter)
|
| 357 |
+
random_metrics = metrics["splits"]["episode_grouped_random"]
|
| 358 |
+
print(f"episode_grouped_random_macro_f1: {random_metrics['macro_f1']:.4f}")
|
| 359 |
+
print(f"episode_grouped_random_large_recall: {random_metrics['large_training']['recall']:.4f}")
|
| 360 |
+
print(f"stress_metrics: {args.model_run / 'stress_metrics.json'}")
|
| 361 |
+
print(f"stress_summary: {args.model_run / 'stress_summary.md'}")
|
| 362 |
+
return 0
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
if __name__ == "__main__":
|
| 366 |
+
raise SystemExit(main())
|
src/datacenter_verification_modeling/predict.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generate calibrated and governance post-processed predictions."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from .evaluate_model import predict_for_model_run
|
| 10 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 11 |
+
from evaluate_model import predict_for_model_run
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main(argv: list[str] | None = None) -> int:
|
| 15 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 16 |
+
parser.add_argument("--model-run", type=Path, required=True)
|
| 17 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 18 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 19 |
+
args = parser.parse_args(argv)
|
| 20 |
+
_, predictions = predict_for_model_run(args.model_run, args.features)
|
| 21 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 22 |
+
predictions.to_csv(args.output, index=False)
|
| 23 |
+
print(f"wrote_predictions: {args.output}")
|
| 24 |
+
print(f"rows: {len(predictions)}")
|
| 25 |
+
return 0
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
if __name__ == "__main__":
|
| 29 |
+
raise SystemExit(main())
|
| 30 |
+
|
src/datacenter_verification_modeling/prepare_features.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prepare leakage-safe feature metadata for the synthetic v0 modeling run."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from .common import DEFAULT_SEED, determine_feature_columns, ensure_dir, load_feature_table, make_episode_split, write_json
|
| 10 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 11 |
+
from common import DEFAULT_SEED, determine_feature_columns, ensure_dir, load_feature_table, make_episode_split, write_json
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def prepare_features(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED) -> dict[str, object]:
|
| 15 |
+
df = load_feature_table(features_path)
|
| 16 |
+
split_df, split_manifest = make_episode_split(df, seed=seed)
|
| 17 |
+
feature_columns, excluded_columns = determine_feature_columns(split_df)
|
| 18 |
+
|
| 19 |
+
ensure_dir(output_dir)
|
| 20 |
+
write_json(output_dir / "split_manifest.json", split_manifest)
|
| 21 |
+
write_json(output_dir / "feature_columns.json", feature_columns)
|
| 22 |
+
write_json(output_dir / "excluded_columns.json", excluded_columns)
|
| 23 |
+
|
| 24 |
+
return {
|
| 25 |
+
"rows": int(len(split_df)),
|
| 26 |
+
"episodes": int(split_df["episode_id"].nunique()),
|
| 27 |
+
"feature_count": int(len(feature_columns)),
|
| 28 |
+
"split_summary": split_manifest["summary"],
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def main(argv: list[str] | None = None) -> int:
|
| 33 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 34 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 35 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 36 |
+
parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
|
| 37 |
+
args = parser.parse_args(argv)
|
| 38 |
+
summary = prepare_features(args.features, args.output, seed=args.seed)
|
| 39 |
+
print(f"rows: {summary['rows']}")
|
| 40 |
+
print(f"episodes: {summary['episodes']}")
|
| 41 |
+
print(f"model_features: {summary['feature_count']}")
|
| 42 |
+
for split, values in summary["split_summary"].items():
|
| 43 |
+
print(f"{split}: rows={values['rows']} episodes={values['episodes']}")
|
| 44 |
+
return 0
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
raise SystemExit(main())
|
| 49 |
+
|
src/datacenter_verification_modeling/requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# python >= 3.10
|
| 2 |
+
pandas
|
| 3 |
+
numpy
|
| 4 |
+
scikit-learn
|
| 5 |
+
joblib
|
src/datacenter_verification_modeling/rule_baseline.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Deterministic evidence-rule baseline for synthetic v0.
|
| 2 |
+
|
| 3 |
+
The rule baseline: not intended to replace the supervised model; it provides a leakage-free point of comparison and encodes the study's governance constraints around capacity, missingness, and multi-layer evidence
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import argparse
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Any
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import pandas as pd
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from .common import LABELS, PROB_COLUMNS, as_bool, load_feature_table, normalize_probability_array, row_bool, row_float
|
| 17 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 18 |
+
from common import LABELS, PROB_COLUMNS, as_bool, load_feature_table, normalize_probability_array, row_bool, row_float
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
FALSE_POSITIVE_RUNTIME_MARKERS = [
|
| 22 |
+
"inference",
|
| 23 |
+
"hpc",
|
| 24 |
+
"mpi",
|
| 25 |
+
"nccl",
|
| 26 |
+
"benchmark",
|
| 27 |
+
"burn_in",
|
| 28 |
+
"storage",
|
| 29 |
+
"etl",
|
| 30 |
+
"synthetic_data",
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
FALSE_POSITIVE_DECLARED_CLASSES = {
|
| 34 |
+
"inference",
|
| 35 |
+
"hpc",
|
| 36 |
+
"benchmark",
|
| 37 |
+
"burn_in",
|
| 38 |
+
"data",
|
| 39 |
+
"reserved",
|
| 40 |
+
"none",
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def evidence_flags(row: pd.Series) -> dict[str, bool]:
|
| 45 |
+
capacity = row_bool(row, "capacity_possible")
|
| 46 |
+
external_conflict = row_float(row, "o17_external_capacity_conflict_score")
|
| 47 |
+
allocation_gpus = row_float(row, "o2_max_concurrent_normalized_gpus")
|
| 48 |
+
allocation_hours = row_float(row, "o2_allocation_duration_hours")
|
| 49 |
+
gpu_hours_ratio = row_float(row, "o2_gpu_hours_policy_ratio")
|
| 50 |
+
policy_ratio = row_float(row, "policy_compute_ratio")
|
| 51 |
+
gpu_util = row_float(row, "o4_gpu_util_p95")
|
| 52 |
+
gpu_duty = row_float(row, "o4_gpu_util_duty_gt_70")
|
| 53 |
+
tensor = row_float(row, "o4_sm_tensor_active_p95")
|
| 54 |
+
fabric_footprint = row_float(row, "o7_synchronized_fabric_footprint")
|
| 55 |
+
fabric_periodicity = row_float(row, "o7_collective_periodicity_score")
|
| 56 |
+
fabric_util = row_float(row, "o7_scaleout_port_util_p95")
|
| 57 |
+
rack_power = row_float(row, "o8_rack_power_fraction_p95")
|
| 58 |
+
checkpoint = row_float(row, "o11_checkpoint_periodicity_score")
|
| 59 |
+
checkpoint_tb = row_float(row, "o11_checkpoint_write_tb_per_event")
|
| 60 |
+
storage_pattern = row_float(row, "o11_read_write_training_pattern_score")
|
| 61 |
+
signed_logs = row_bool(row, "o12_signed_ml_logs_present")
|
| 62 |
+
declared_params = row_float(row, "o12_declared_parameter_count_b")
|
| 63 |
+
training_tokens = row_float(row, "o12_training_tokens_b")
|
| 64 |
+
optimizer_state = row_bool(row, "o12_optimizer_state_present")
|
| 65 |
+
min_coverage = row_float(row, "o14_min_critical_coverage", 1.0)
|
| 66 |
+
gap_fraction = row_float(row, "o14_gap_fraction_critical")
|
| 67 |
+
cc_fraction = row_float(row, "o13_confidential_compute_mode_fraction")
|
| 68 |
+
counter_resets = row_float(row, "o14_counter_reset_count")
|
| 69 |
+
runtime = str(row["o10_runtime_framework_class"]).lower() if "o10_runtime_framework_class" in row else ""
|
| 70 |
+
declared_class = str(row["o2_declared_workload_class"]).lower() if "o2_declared_workload_class" in row else ""
|
| 71 |
+
|
| 72 |
+
allocation = allocation_gpus >= 512 or gpu_hours_ratio >= 1.0 or policy_ratio >= 1.0 or (
|
| 73 |
+
allocation_gpus >= 256 and allocation_hours >= 12
|
| 74 |
+
)
|
| 75 |
+
gpu_activity = gpu_util >= 70 or gpu_duty >= 0.45 or tensor >= 0.60
|
| 76 |
+
fabric_sync = fabric_footprint >= 256 or fabric_periodicity >= 0.55 or fabric_util >= 0.65
|
| 77 |
+
physical_support = rack_power >= 0.55
|
| 78 |
+
storage_semantic = checkpoint >= 0.55 or checkpoint_tb >= 0.25 or storage_pattern >= 0.60
|
| 79 |
+
runtime_semantic = "training" in runtime or "fine_tune" in runtime or "pytorch_distributed" in runtime
|
| 80 |
+
ml_semantic = signed_logs or declared_params >= 50 or training_tokens >= 100 or optimizer_state
|
| 81 |
+
integrity = (
|
| 82 |
+
gap_fraction > 0.05
|
| 83 |
+
or min_coverage < 0.80
|
| 84 |
+
or cc_fraction > 0.50
|
| 85 |
+
or counter_resets > 0
|
| 86 |
+
or row_bool(row, "o15_unapproved_physical_change_near_window")
|
| 87 |
+
or str(row.get("o4_missing_reason", "")) == "counter_disabled_by_cc_mode"
|
| 88 |
+
)
|
| 89 |
+
false_positive_pattern = any(marker in runtime for marker in FALSE_POSITIVE_RUNTIME_MARKERS) or declared_class in (
|
| 90 |
+
FALSE_POSITIVE_DECLARED_CLASSES
|
| 91 |
+
)
|
| 92 |
+
reserved_without_activity = (
|
| 93 |
+
allocation_gpus >= 512
|
| 94 |
+
and gpu_util < 30
|
| 95 |
+
and fabric_footprint < 64
|
| 96 |
+
and fabric_periodicity < 0.20
|
| 97 |
+
and not storage_semantic
|
| 98 |
+
and not ml_semantic
|
| 99 |
+
)
|
| 100 |
+
return {
|
| 101 |
+
"capacity": capacity,
|
| 102 |
+
"external_conflict": external_conflict >= 0.5,
|
| 103 |
+
"allocation": allocation,
|
| 104 |
+
"gpu_activity": gpu_activity,
|
| 105 |
+
"fabric_sync": fabric_sync,
|
| 106 |
+
"physical_support": physical_support,
|
| 107 |
+
"storage_semantic": storage_semantic,
|
| 108 |
+
"runtime_semantic": runtime_semantic,
|
| 109 |
+
"ml_semantic": ml_semantic,
|
| 110 |
+
"signed_logs": signed_logs,
|
| 111 |
+
"integrity": integrity,
|
| 112 |
+
"false_positive_pattern": false_positive_pattern,
|
| 113 |
+
"reserved_without_activity": reserved_without_activity,
|
| 114 |
+
"strong_coverage": min_coverage >= 0.90 and gap_fraction <= 0.05,
|
| 115 |
+
"policy_scale": policy_ratio >= 1.0 or gpu_hours_ratio >= 1.0 or allocation_gpus >= 512,
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def predict_rule_label(row: pd.Series) -> int:
|
| 120 |
+
flags = evidence_flags(row)
|
| 121 |
+
primary_count = int(flags["allocation"]) + int(flags["gpu_activity"]) + int(flags["fabric_sync"])
|
| 122 |
+
semantic_count = int(flags["runtime_semantic"]) + int(flags["storage_semantic"]) + int(flags["ml_semantic"])
|
| 123 |
+
|
| 124 |
+
if not flags["capacity"] and not flags["external_conflict"]:
|
| 125 |
+
return 0 if flags["strong_coverage"] else 1
|
| 126 |
+
|
| 127 |
+
if flags["signed_logs"] and flags["policy_scale"] and primary_count >= 2:
|
| 128 |
+
return 4
|
| 129 |
+
|
| 130 |
+
if (
|
| 131 |
+
flags["allocation"]
|
| 132 |
+
and flags["gpu_activity"]
|
| 133 |
+
and flags["fabric_sync"]
|
| 134 |
+
and flags["physical_support"]
|
| 135 |
+
and semantic_count >= 2
|
| 136 |
+
and flags["policy_scale"]
|
| 137 |
+
):
|
| 138 |
+
return 4
|
| 139 |
+
|
| 140 |
+
if flags["reserved_without_activity"]:
|
| 141 |
+
return 1
|
| 142 |
+
|
| 143 |
+
if flags["false_positive_pattern"] and (flags["gpu_activity"] or flags["fabric_sync"] or flags["physical_support"]):
|
| 144 |
+
return 2
|
| 145 |
+
|
| 146 |
+
if flags["integrity"] and not flags["ml_semantic"] and (primary_count >= 1 or flags["physical_support"]):
|
| 147 |
+
return 2
|
| 148 |
+
|
| 149 |
+
if primary_count >= 2 and (flags["physical_support"] or semantic_count >= 1):
|
| 150 |
+
return 3
|
| 151 |
+
|
| 152 |
+
if primary_count >= 1 or flags["physical_support"] or semantic_count >= 1:
|
| 153 |
+
return 2
|
| 154 |
+
|
| 155 |
+
return 1 if flags["capacity"] else 0
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def predict_rule_labels(df: pd.DataFrame) -> pd.Series:
|
| 159 |
+
return df.apply(predict_rule_label, axis=1).astype(int)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def predict_rule_probabilities(df: pd.DataFrame, confidence: float = 0.82) -> pd.DataFrame:
|
| 163 |
+
labels = predict_rule_labels(df)
|
| 164 |
+
off_probability = (1.0 - confidence) / (len(LABELS) - 1)
|
| 165 |
+
probabilities = np.full((len(df), len(LABELS)), off_probability, dtype=float)
|
| 166 |
+
for row_index, label in enumerate(labels):
|
| 167 |
+
probabilities[row_index, LABELS.index(int(label))] = confidence
|
| 168 |
+
probabilities = normalize_probability_array(probabilities)
|
| 169 |
+
return pd.DataFrame(probabilities, columns=PROB_COLUMNS, index=df.index)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def main(argv: list[str] | None = None) -> int:
|
| 173 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 174 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 175 |
+
parser.add_argument("--output", type=Path)
|
| 176 |
+
args = parser.parse_args(argv)
|
| 177 |
+
|
| 178 |
+
df = load_feature_table(args.features)
|
| 179 |
+
labels = predict_rule_labels(df)
|
| 180 |
+
out = df[["feature_row_id", "episode_id", "latent_workload_class", "label_0_to_4"]].copy()
|
| 181 |
+
out["rule_predicted_label"] = labels
|
| 182 |
+
if args.output:
|
| 183 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 184 |
+
out.to_csv(args.output, index=False)
|
| 185 |
+
else:
|
| 186 |
+
print(out.head(20).to_string(index=False))
|
| 187 |
+
return 0
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
raise SystemExit(main())
|
| 192 |
+
|
src/datacenter_verification_modeling/train_model.py
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train the calibrated supervised baseline for synthetic datacenter verification data."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import joblib
|
| 12 |
+
import numpy as np
|
| 13 |
+
from sklearn.calibration import CalibratedClassifierCV
|
| 14 |
+
from sklearn.ensemble import HistGradientBoostingClassifier
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
from .common import (
|
| 18 |
+
DEFAULT_SEED,
|
| 19 |
+
determine_feature_columns,
|
| 20 |
+
derive_dataset_dir,
|
| 21 |
+
ensure_dir,
|
| 22 |
+
load_feature_table,
|
| 23 |
+
make_episode_split,
|
| 24 |
+
make_preprocessor,
|
| 25 |
+
model_input_frame,
|
| 26 |
+
utc_now_iso,
|
| 27 |
+
write_json,
|
| 28 |
+
)
|
| 29 |
+
from .evaluate_model import evaluate_model_run
|
| 30 |
+
except ImportError: # pragma: no cover - direct script execution
|
| 31 |
+
from common import (
|
| 32 |
+
DEFAULT_SEED,
|
| 33 |
+
determine_feature_columns,
|
| 34 |
+
derive_dataset_dir,
|
| 35 |
+
ensure_dir,
|
| 36 |
+
load_feature_table,
|
| 37 |
+
make_episode_split,
|
| 38 |
+
make_preprocessor,
|
| 39 |
+
model_input_frame,
|
| 40 |
+
utc_now_iso,
|
| 41 |
+
write_json,
|
| 42 |
+
)
|
| 43 |
+
from evaluate_model import evaluate_model_run
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def repo_root() -> Path:
|
| 47 |
+
return Path(__file__).resolve().parents[2]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def run_dataset_validations(features_path: Path) -> list[dict[str, Any]]:
|
| 51 |
+
root = repo_root()
|
| 52 |
+
dataset_dir = derive_dataset_dir(features_path)
|
| 53 |
+
try:
|
| 54 |
+
dataset_arg = dataset_dir.relative_to(root)
|
| 55 |
+
except ValueError:
|
| 56 |
+
dataset_arg = dataset_dir
|
| 57 |
+
commands = [
|
| 58 |
+
{
|
| 59 |
+
"name": "synthetic_dataset_validator",
|
| 60 |
+
"command": [
|
| 61 |
+
sys.executable,
|
| 62 |
+
str(root / "src/datacenter_verification_synthetic/validate_synthetic_dataset.py"),
|
| 63 |
+
"--dataset",
|
| 64 |
+
str(dataset_arg),
|
| 65 |
+
],
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"name": "public_dataset_validator",
|
| 69 |
+
"command": [
|
| 70 |
+
sys.executable,
|
| 71 |
+
"-m",
|
| 72 |
+
"src.datacenter_verification_validators",
|
| 73 |
+
"--dataset",
|
| 74 |
+
str(dataset_arg),
|
| 75 |
+
],
|
| 76 |
+
},
|
| 77 |
+
]
|
| 78 |
+
results: list[dict[str, Any]] = []
|
| 79 |
+
for item in commands:
|
| 80 |
+
completed = subprocess.run(
|
| 81 |
+
item["command"],
|
| 82 |
+
cwd=root,
|
| 83 |
+
check=False,
|
| 84 |
+
text=True,
|
| 85 |
+
capture_output=True,
|
| 86 |
+
)
|
| 87 |
+
result = {
|
| 88 |
+
"name": item["name"],
|
| 89 |
+
"command": " ".join(item["command"]),
|
| 90 |
+
"returncode": int(completed.returncode),
|
| 91 |
+
"stdout": completed.stdout.strip(),
|
| 92 |
+
"stderr": completed.stderr.strip(),
|
| 93 |
+
}
|
| 94 |
+
results.append(result)
|
| 95 |
+
if completed.returncode != 0:
|
| 96 |
+
raise RuntimeError(
|
| 97 |
+
f"dataset validation failed for {item['name']} with return code {completed.returncode}\n"
|
| 98 |
+
f"stdout:\n{completed.stdout}\n\nstderr:\n{completed.stderr}"
|
| 99 |
+
)
|
| 100 |
+
return results
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def make_base_classifier(seed: int) -> HistGradientBoostingClassifier:
|
| 104 |
+
kwargs: dict[str, Any] = {
|
| 105 |
+
"learning_rate": 0.05,
|
| 106 |
+
"max_iter": 350,
|
| 107 |
+
"max_leaf_nodes": 31,
|
| 108 |
+
"l2_regularization": 0.03,
|
| 109 |
+
"early_stopping": True,
|
| 110 |
+
"validation_fraction": 0.15,
|
| 111 |
+
"random_state": seed,
|
| 112 |
+
}
|
| 113 |
+
try:
|
| 114 |
+
return HistGradientBoostingClassifier(class_weight="balanced", **kwargs)
|
| 115 |
+
except TypeError: # pragma: no cover - older scikit-learn fallback
|
| 116 |
+
return HistGradientBoostingClassifier(**kwargs)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def calibrate_prefit_model(base_model: Any, x_validation: np.ndarray, y_validation: np.ndarray) -> tuple[Any, str]:
|
| 120 |
+
try:
|
| 121 |
+
from sklearn.frozen import FrozenEstimator
|
| 122 |
+
|
| 123 |
+
calibrator = CalibratedClassifierCV(FrozenEstimator(base_model), method="sigmoid")
|
| 124 |
+
method = "sigmoid_on_validation_split_frozen_estimator"
|
| 125 |
+
except Exception: # pragma: no cover - older scikit-learn fallback
|
| 126 |
+
calibrator = CalibratedClassifierCV(base_model, method="sigmoid", cv="prefit")
|
| 127 |
+
method = "sigmoid_on_validation_split_prefit"
|
| 128 |
+
calibrator.fit(x_validation, y_validation)
|
| 129 |
+
return calibrator, method
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def train_model(features_path: Path, output_dir: Path, seed: int = DEFAULT_SEED, skip_dataset_validation: bool = False) -> dict[str, Any]:
|
| 133 |
+
ensure_dir(output_dir)
|
| 134 |
+
validation_status = [] if skip_dataset_validation else run_dataset_validations(features_path)
|
| 135 |
+
|
| 136 |
+
df = load_feature_table(features_path)
|
| 137 |
+
split_df, split_manifest = make_episode_split(df, seed=seed)
|
| 138 |
+
feature_columns, excluded_columns = determine_feature_columns(split_df)
|
| 139 |
+
write_json(output_dir / "split_manifest.json", split_manifest)
|
| 140 |
+
write_json(output_dir / "feature_columns.json", feature_columns)
|
| 141 |
+
write_json(output_dir / "excluded_columns.json", excluded_columns)
|
| 142 |
+
|
| 143 |
+
train_df = split_df[split_df["split"] == "train"].copy()
|
| 144 |
+
validation_df = split_df[split_df["split"] == "validation"].copy()
|
| 145 |
+
if train_df.empty or validation_df.empty:
|
| 146 |
+
raise ValueError("train and validation splits must both be non-empty")
|
| 147 |
+
|
| 148 |
+
preprocessor = make_preprocessor(train_df, feature_columns)
|
| 149 |
+
x_train = preprocessor.fit_transform(model_input_frame(train_df, feature_columns))
|
| 150 |
+
y_train = train_df["label_0_to_4"].astype(int).to_numpy()
|
| 151 |
+
x_validation = preprocessor.transform(model_input_frame(validation_df, feature_columns))
|
| 152 |
+
y_validation = validation_df["label_0_to_4"].astype(int).to_numpy()
|
| 153 |
+
|
| 154 |
+
base_model = make_base_classifier(seed)
|
| 155 |
+
base_model.fit(x_train, y_train)
|
| 156 |
+
calibrated_model, calibration_method = calibrate_prefit_model(base_model, x_validation, y_validation)
|
| 157 |
+
|
| 158 |
+
joblib.dump(calibrated_model, output_dir / "model.joblib")
|
| 159 |
+
joblib.dump(preprocessor, output_dir / "preprocessing.joblib")
|
| 160 |
+
|
| 161 |
+
training_metadata = {
|
| 162 |
+
"trained_at": utc_now_iso(),
|
| 163 |
+
"seed": int(seed),
|
| 164 |
+
"features_path": str(features_path),
|
| 165 |
+
"model_type": "HistGradientBoostingClassifier",
|
| 166 |
+
"model_parameters": base_model.get_params(),
|
| 167 |
+
"calibration_method": calibration_method,
|
| 168 |
+
"train_rows": int(len(train_df)),
|
| 169 |
+
"validation_rows": int(len(validation_df)),
|
| 170 |
+
"train_episodes": int(train_df["episode_id"].nunique()),
|
| 171 |
+
"validation_episodes": int(validation_df["episode_id"].nunique()),
|
| 172 |
+
"feature_count": int(len(feature_columns)),
|
| 173 |
+
"dataset_validation_status": validation_status,
|
| 174 |
+
}
|
| 175 |
+
write_json(
|
| 176 |
+
output_dir / "manifest.json",
|
| 177 |
+
{
|
| 178 |
+
"model_run_id": output_dir.name,
|
| 179 |
+
"created_or_updated_at": utc_now_iso(),
|
| 180 |
+
"features_path": str(features_path),
|
| 181 |
+
"model_type": "CalibratedClassifierCV over HistGradientBoostingClassifier",
|
| 182 |
+
"calibration_method": calibration_method,
|
| 183 |
+
"training_metadata": training_metadata,
|
| 184 |
+
"validation_status": validation_status,
|
| 185 |
+
},
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
metrics = evaluate_model_run(
|
| 189 |
+
output_dir,
|
| 190 |
+
features_path,
|
| 191 |
+
validation_status=validation_status,
|
| 192 |
+
training_metadata=training_metadata,
|
| 193 |
+
)
|
| 194 |
+
return {
|
| 195 |
+
"metrics": metrics,
|
| 196 |
+
"split_summary": split_manifest["summary"],
|
| 197 |
+
"validation_status": validation_status,
|
| 198 |
+
"training_metadata": training_metadata,
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def main(argv: list[str] | None = None) -> int:
|
| 203 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 204 |
+
parser.add_argument("--features", type=Path, required=True)
|
| 205 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 206 |
+
parser.add_argument("--seed", type=int, default=DEFAULT_SEED)
|
| 207 |
+
parser.add_argument(
|
| 208 |
+
"--skip-dataset-validation",
|
| 209 |
+
action="store_true",
|
| 210 |
+
help="Skip pre-training dataset validation. Intended only for development.",
|
| 211 |
+
)
|
| 212 |
+
args = parser.parse_args(argv)
|
| 213 |
+
result = train_model(args.features, args.output, seed=args.seed, skip_dataset_validation=args.skip_dataset_validation)
|
| 214 |
+
metrics = result["metrics"]
|
| 215 |
+
print(f"train_rows: {result['split_summary']['train']['rows']}")
|
| 216 |
+
print(f"validation_rows: {result['split_summary']['validation']['rows']}")
|
| 217 |
+
print(f"test_rows: {result['split_summary']['test']['rows']}")
|
| 218 |
+
print(f"accuracy: {metrics['model']['accuracy']:.4f}")
|
| 219 |
+
print(f"macro_f1: {metrics['model']['macro_f1']:.4f}")
|
| 220 |
+
print(f"label_3_4_precision: {metrics['governance']['label_3_4_predicted_label']['precision']:.4f}")
|
| 221 |
+
print(f"label_3_4_recall: {metrics['governance']['label_3_4_predicted_label']['recall']:.4f}")
|
| 222 |
+
return 0
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
if __name__ == "__main__":
|
| 226 |
+
raise SystemExit(main())
|