acprof 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acprof/__init__.py +3 -0
- acprof/__main__.py +5 -0
- acprof/_bundle/.dockerignore +20 -0
- acprof/_bundle/LICENSE +202 -0
- acprof/_bundle/NOTICE +43 -0
- acprof/_bundle/acprof/__init__.py +3 -0
- acprof/_bundle/acprof/__main__.py +5 -0
- acprof/_bundle/acprof/analysis/__init__.py +1 -0
- acprof/_bundle/acprof/analysis/audit.py +276 -0
- acprof/_bundle/acprof/analysis/comparison.py +364 -0
- acprof/_bundle/acprof/analysis/compatibility.py +223 -0
- acprof/_bundle/acprof/analysis/conditions.py +185 -0
- acprof/_bundle/acprof/analysis/independent_comparison.py +191 -0
- acprof/_bundle/acprof/analysis/latency_model.py +1106 -0
- acprof/_bundle/acprof/analysis/latency_report.py +577 -0
- acprof/_bundle/acprof/analysis/model.py +276 -0
- acprof/_bundle/acprof/analysis/precision.py +51 -0
- acprof/_bundle/acprof/analysis/uncertainty.py +144 -0
- acprof/_bundle/acprof/application/__init__.py +1 -0
- acprof/_bundle/acprof/application/download.py +14 -0
- acprof/_bundle/acprof/application/errors.py +5 -0
- acprof/_bundle/acprof/application/notifications.py +302 -0
- acprof/_bundle/acprof/application/preparation.py +333 -0
- acprof/_bundle/acprof/application/resources.py +66 -0
- acprof/_bundle/acprof/application/run.py +462 -0
- acprof/_bundle/acprof/application/terminal_log.py +136 -0
- acprof/_bundle/acprof/artifact_layout.py +261 -0
- acprof/_bundle/acprof/artifacts.py +161 -0
- acprof/_bundle/acprof/capabilities.py +404 -0
- acprof/_bundle/acprof/cli/__init__.py +1 -0
- acprof/_bundle/acprof/cli/audit.py +74 -0
- acprof/_bundle/acprof/cli/auto.py +41 -0
- acprof/_bundle/acprof/cli/backfill_compute.py +498 -0
- acprof/_bundle/acprof/cli/compare.py +34 -0
- acprof/_bundle/acprof/cli/coverage.py +70 -0
- acprof/_bundle/acprof/cli/doctor.py +47 -0
- acprof/_bundle/acprof/cli/inspect.py +101 -0
- acprof/_bundle/acprof/cli/load.py +190 -0
- acprof/_bundle/acprof/cli/main.py +64 -0
- acprof/_bundle/acprof/cli/model_store.py +22 -0
- acprof/_bundle/acprof/cli/plot.py +211 -0
- acprof/_bundle/acprof/cli/posthoc.py +113 -0
- acprof/_bundle/acprof/cli/probe.py +186 -0
- acprof/_bundle/acprof/cli/report.py +35 -0
- acprof/_bundle/acprof/cli/results.py +37 -0
- acprof/_bundle/acprof/cli/run.py +54 -0
- acprof/_bundle/acprof/cli/stats.py +94 -0
- acprof/_bundle/acprof/cli/tui.py +65 -0
- acprof/_bundle/acprof/config.py +177 -0
- acprof/_bundle/acprof/container/__init__.py +1 -0
- acprof/_bundle/acprof/container/compat/__init__.py +1 -0
- acprof/_bundle/acprof/container/compat/transformers_dynamic.py +54 -0
- acprof/_bundle/acprof/container/compat/transformers_model_types.json +1091 -0
- acprof/_bundle/acprof/container/compute_profile_runner.py +392 -0
- acprof/_bundle/acprof/container/download_model.py +165 -0
- acprof/_bundle/acprof/container/execution.py +73 -0
- acprof/_bundle/acprof/container/handlers/__init__.py +298 -0
- acprof/_bundle/acprof/container/handlers/audio.py +743 -0
- acprof/_bundle/acprof/container/handlers/custom_pipeline.py +74 -0
- acprof/_bundle/acprof/container/handlers/cv.py +321 -0
- acprof/_bundle/acprof/container/handlers/diffusion.py +728 -0
- acprof/_bundle/acprof/container/handlers/moss.py +64 -0
- acprof/_bundle/acprof/container/handlers/multimodal.py +609 -0
- acprof/_bundle/acprof/container/handlers/nlp.py +747 -0
- acprof/_bundle/acprof/container/handlers/onnx_tasks.py +231 -0
- acprof/_bundle/acprof/container/handlers/onnxruntime.py +90 -0
- acprof/_bundle/acprof/container/handlers/structured.py +185 -0
- acprof/_bundle/acprof/container/handlers/timeseries.py +178 -0
- acprof/_bundle/acprof/container/load_policy.py +81 -0
- acprof/_bundle/acprof/container/loading_quality.py +48 -0
- acprof/_bundle/acprof/container/local_pipeline.py +31 -0
- acprof/_bundle/acprof/container/model_files.py +280 -0
- acprof/_bundle/acprof/container/model_probe.py +80 -0
- acprof/_bundle/acprof/container/onnx_session.py +150 -0
- acprof/_bundle/acprof/container/runtime_manifest.py +150 -0
- acprof/_bundle/acprof/container/runtime_validate.py +156 -0
- acprof/_bundle/acprof/container/server.py +209 -0
- acprof/_bundle/acprof/container/torch_execution.py +55 -0
- acprof/_bundle/acprof/container/validation.py +172 -0
- acprof/_bundle/acprof/cpu_affinity.py +35 -0
- acprof/_bundle/acprof/dependency_locks.py +129 -0
- acprof/_bundle/acprof/experiment.py +356 -0
- acprof/_bundle/acprof/extensions/__init__.py +294 -0
- acprof/_bundle/acprof/extensions/builtin/manifest.json +3591 -0
- acprof/_bundle/acprof/extensions/onnxruntime/manifest.json +191 -0
- acprof/_bundle/acprof/extensions/schema.py +238 -0
- acprof/_bundle/acprof/extensions/transformers/4.57.6.json +1639 -0
- acprof/_bundle/acprof/extensions/transformers/5.10.4.json +1812 -0
- acprof/_bundle/acprof/failures.py +115 -0
- acprof/_bundle/acprof/hardware_conditions.py +18 -0
- acprof/_bundle/acprof/hf_download.py +143 -0
- acprof/_bundle/acprof/hf_endpoints.py +62 -0
- acprof/_bundle/acprof/hf_transport.py +238 -0
- acprof/_bundle/acprof/host/__init__.py +1 -0
- acprof/_bundle/acprof/host/automation.py +161 -0
- acprof/_bundle/acprof/host/client.py +520 -0
- acprof/_bundle/acprof/host/collection_history.py +77 -0
- acprof/_bundle/acprof/host/collection_workflow.py +285 -0
- acprof/_bundle/acprof/host/command.py +119 -0
- acprof/_bundle/acprof/host/container_lifecycle.py +225 -0
- acprof/_bundle/acprof/host/container_state.py +128 -0
- acprof/_bundle/acprof/host/coverage_state.py +259 -0
- acprof/_bundle/acprof/host/detect.py +669 -0
- acprof/_bundle/acprof/host/docker_runtime.py +359 -0
- acprof/_bundle/acprof/host/doctor.py +158 -0
- acprof/_bundle/acprof/host/env_utils.py +233 -0
- acprof/_bundle/acprof/host/execution_conditions.py +87 -0
- acprof/_bundle/acprof/host/gpu_device.py +66 -0
- acprof/_bundle/acprof/host/hardware_conditions.py +130 -0
- acprof/_bundle/acprof/host/images/__init__.py +1 -0
- acprof/_bundle/acprof/host/images/dependencies.py +169 -0
- acprof/_bundle/acprof/host/images/environment.py +313 -0
- acprof/_bundle/acprof/host/images/graph.py +143 -0
- acprof/_bundle/acprof/host/images/identity.py +6 -0
- acprof/_bundle/acprof/host/images/inventory.py +445 -0
- acprof/_bundle/acprof/host/images/runtime.py +457 -0
- acprof/_bundle/acprof/host/images/scan.py +154 -0
- acprof/_bundle/acprof/host/input_plan.py +1056 -0
- acprof/_bundle/acprof/host/interface_probe.py +141 -0
- acprof/_bundle/acprof/host/largest_scale_probe.py +566 -0
- acprof/_bundle/acprof/host/matrix_plan.py +98 -0
- acprof/_bundle/acprof/host/measurement/__init__.py +1 -0
- acprof/_bundle/acprof/host/measurement/boundary.py +162 -0
- acprof/_bundle/acprof/host/measurement/capture.py +139 -0
- acprof/_bundle/acprof/host/measurement/checkpoint.py +275 -0
- acprof/_bundle/acprof/host/measurement/config.py +212 -0
- acprof/_bundle/acprof/host/measurement/diagnostics.py +247 -0
- acprof/_bundle/acprof/host/measurement/energy_validation.py +172 -0
- acprof/_bundle/acprof/host/measurement/errors.py +25 -0
- acprof/_bundle/acprof/host/measurement/execution.py +396 -0
- acprof/_bundle/acprof/host/measurement/load.py +237 -0
- acprof/_bundle/acprof/host/measurement/metrics.py +975 -0
- acprof/_bundle/acprof/host/measurement/monitors.py +143 -0
- acprof/_bundle/acprof/host/measurement/publication.py +171 -0
- acprof/_bundle/acprof/host/measurement/requests.py +97 -0
- acprof/_bundle/acprof/host/measurement/types.py +82 -0
- acprof/_bundle/acprof/host/model_coverage.py +337 -0
- acprof/_bundle/acprof/host/model_errors.py +116 -0
- acprof/_bundle/acprof/host/model_inspection.py +107 -0
- acprof/_bundle/acprof/host/model_schema.py +35 -0
- acprof/_bundle/acprof/host/model_store.py +733 -0
- acprof/_bundle/acprof/host/network_preflight.py +229 -0
- acprof/_bundle/acprof/host/orchestrator.py +1133 -0
- acprof/_bundle/acprof/host/permissions.py +117 -0
- acprof/_bundle/acprof/host/platform_metadata.py +63 -0
- acprof/_bundle/acprof/host/posthoc/__init__.py +12 -0
- acprof/_bundle/acprof/host/posthoc/backfill.py +415 -0
- acprof/_bundle/acprof/host/posthoc/context.py +400 -0
- acprof/_bundle/acprof/host/posthoc/plans.py +597 -0
- acprof/_bundle/acprof/host/posthoc/service.py +303 -0
- acprof/_bundle/acprof/host/posthoc/storage.py +491 -0
- acprof/_bundle/acprof/host/preflight.py +370 -0
- acprof/_bundle/acprof/host/profiling/__init__.py +1 -0
- acprof/_bundle/acprof/host/profiling/compute.py +436 -0
- acprof/_bundle/acprof/host/profiling/compute_plan.py +261 -0
- acprof/_bundle/acprof/host/profiling/execution.py +547 -0
- acprof/_bundle/acprof/host/profiling/execution_plan.py +576 -0
- acprof/_bundle/acprof/host/profiling/progress.py +83 -0
- acprof/_bundle/acprof/host/profiling/support.py +252 -0
- acprof/_bundle/acprof/host/profiling/tools/__init__.py +1 -0
- acprof/_bundle/acprof/host/profiling/tools/advisor.py +164 -0
- acprof/_bundle/acprof/host/profiling/tools/compute_parsers.py +409 -0
- acprof/_bundle/acprof/host/profiling/tools/execution_environment.py +135 -0
- acprof/_bundle/acprof/host/profiling/tools/execution_parsers.py +364 -0
- acprof/_bundle/acprof/host/profiling/tools/massif.py +418 -0
- acprof/_bundle/acprof/host/profiling/tools/ncu.py +696 -0
- acprof/_bundle/acprof/host/profiling/tools/nsys.py +326 -0
- acprof/_bundle/acprof/host/profiling/tools/tool_discovery.py +229 -0
- acprof/_bundle/acprof/host/profiling/tools/torch.py +158 -0
- acprof/_bundle/acprof/host/result_cleanup.py +65 -0
- acprof/_bundle/acprof/host/run_state.py +639 -0
- acprof/_bundle/acprof/host/runtime_settings.py +10 -0
- acprof/_bundle/acprof/host/runtime_validation.py +302 -0
- acprof/_bundle/acprof/host/source_bundle.py +96 -0
- acprof/_bundle/acprof/host/startup_probe.py +95 -0
- acprof/_bundle/acprof/host/static_metadata.py +936 -0
- acprof/_bundle/acprof/host/task_support.py +121 -0
- acprof/_bundle/acprof/installation.py +26 -0
- acprof/_bundle/acprof/latency_slo.py +62 -0
- acprof/_bundle/acprof/messages.py +26 -0
- acprof/_bundle/acprof/metric_registry.py +315 -0
- acprof/_bundle/acprof/model_candidates.py +297 -0
- acprof/_bundle/acprof/model_contract.py +234 -0
- acprof/_bundle/acprof/model_dependencies.py +93 -0
- acprof/_bundle/acprof/model_dependency_domain.py +45 -0
- acprof/_bundle/acprof/model_dependency_eval.py +438 -0
- acprof/_bundle/acprof/model_dependency_flow.py +200 -0
- acprof/_bundle/acprof/model_evidence.py +115 -0
- acprof/_bundle/acprof/model_metadata_analysis.py +156 -0
- acprof/_bundle/acprof/model_repository.py +149 -0
- acprof/_bundle/acprof/model_resolution.py +129 -0
- acprof/_bundle/acprof/model_review.py +155 -0
- acprof/_bundle/acprof/model_source_analysis.py +299 -0
- acprof/_bundle/acprof/model_spec.py +204 -0
- acprof/_bundle/acprof/model_support.py +105 -0
- acprof/_bundle/acprof/model_transforms.py +81 -0
- acprof/_bundle/acprof/monitors/__init__.py +1 -0
- acprof/_bundle/acprof/monitors/common.py +63 -0
- acprof/_bundle/acprof/monitors/energy_cpu.py +651 -0
- acprof/_bundle/acprof/monitors/energy_nvml.py +381 -0
- acprof/_bundle/acprof/monitors/gpu_metrics.py +123 -0
- acprof/_bundle/acprof/monitors/interfaces.py +74 -0
- acprof/_bundle/acprof/monitors/perf_mips.py +617 -0
- acprof/_bundle/acprof/monitors/rapl_topology.py +100 -0
- acprof/_bundle/acprof/monitors/resource_metrics.py +472 -0
- acprof/_bundle/acprof/monitors/resource_readers.py +435 -0
- acprof/_bundle/acprof/monitors/resource_usage.py +450 -0
- acprof/_bundle/acprof/monitors/sampling.py +79 -0
- acprof/_bundle/acprof/network_policy.py +182 -0
- acprof/_bundle/acprof/notifications.py +279 -0
- acprof/_bundle/acprof/packet/__init__.py +1 -0
- acprof/_bundle/acprof/packet/merge_packet_latency.py +369 -0
- acprof/_bundle/acprof/packet/sniff_parse_pcap.py +240 -0
- acprof/_bundle/acprof/pixel_metrics.py +103 -0
- acprof/_bundle/acprof/platform.py +180 -0
- acprof/_bundle/acprof/plotting/__init__.py +1 -0
- acprof/_bundle/acprof/plotting/config.py +487 -0
- acprof/_bundle/acprof/plotting/data.py +236 -0
- acprof/_bundle/acprof/plotting/diagnostics.py +747 -0
- acprof/_bundle/acprof/plotting/latency.py +638 -0
- acprof/_bundle/acprof/plotting/metrics.py +648 -0
- acprof/_bundle/acprof/plotting/plotly-LICENSE.txt +21 -0
- acprof/_bundle/acprof/plotting/report.py +66 -0
- acprof/_bundle/acprof/plotting/styles.py +111 -0
- acprof/_bundle/acprof/precision.py +42 -0
- acprof/_bundle/acprof/preparation_events.py +78 -0
- acprof/_bundle/acprof/progress_events.py +105 -0
- acprof/_bundle/acprof/quality.py +153 -0
- acprof/_bundle/acprof/resource_budget.py +29 -0
- acprof/_bundle/acprof/result_csv.py +263 -0
- acprof/_bundle/acprof/result_layers.py +365 -0
- acprof/_bundle/acprof/run_args.py +223 -0
- acprof/_bundle/acprof/run_options.py +225 -0
- acprof/_bundle/acprof/runtime_dependencies.py +195 -0
- acprof/_bundle/acprof/runtime_profiles.py +258 -0
- acprof/_bundle/acprof/runtime_settings.py +72 -0
- acprof/_bundle/acprof/source_identity.py +68 -0
- acprof/_bundle/acprof/telemetry.py +225 -0
- acprof/_bundle/acprof/testing/__init__.py +1 -0
- acprof/_bundle/acprof/testing/evidence.py +117 -0
- acprof/_bundle/acprof/testing/plugin.py +126 -0
- acprof/_bundle/acprof/testing/provenance.py +53 -0
- acprof/_bundle/acprof/testing/sharding.py +14 -0
- acprof/_bundle/acprof/tui/__init__.py +1 -0
- acprof/_bundle/acprof/tui/app.py +934 -0
- acprof/_bundle/acprof/tui/app_contracts.py +43 -0
- acprof/_bundle/acprof/tui/catalog_controller.py +120 -0
- acprof/_bundle/acprof/tui/commands.py +209 -0
- acprof/_bundle/acprof/tui/configuration_controller.py +436 -0
- acprof/_bundle/acprof/tui/diagnostics.py +340 -0
- acprof/_bundle/acprof/tui/downloads.py +56 -0
- acprof/_bundle/acprof/tui/environment.py +248 -0
- acprof/_bundle/acprof/tui/experiment_catalog.py +279 -0
- acprof/_bundle/acprof/tui/experiment_controller.py +132 -0
- acprof/_bundle/acprof/tui/experiment_picker.py +273 -0
- acprof/_bundle/acprof/tui/field_validation.py +62 -0
- acprof/_bundle/acprof/tui/i18n.py +57 -0
- acprof/_bundle/acprof/tui/image_controller.py +525 -0
- acprof/_bundle/acprof/tui/images.py +737 -0
- acprof/_bundle/acprof/tui/input.py +191 -0
- acprof/_bundle/acprof/tui/localization_controller.py +110 -0
- acprof/_bundle/acprof/tui/log.py +310 -0
- acprof/_bundle/acprof/tui/model_candidates.py +278 -0
- acprof/_bundle/acprof/tui/model_controller.py +111 -0
- acprof/_bundle/acprof/tui/model_input.py +12 -0
- acprof/_bundle/acprof/tui/model_store.py +166 -0
- acprof/_bundle/acprof/tui/preflight_controller.py +271 -0
- acprof/_bundle/acprof/tui/preparation.py +269 -0
- acprof/_bundle/acprof/tui/presentation.py +46 -0
- acprof/_bundle/acprof/tui/process.py +111 -0
- acprof/_bundle/acprof/tui/process_controller.py +218 -0
- acprof/_bundle/acprof/tui/profile_controller.py +109 -0
- acprof/_bundle/acprof/tui/progress.py +195 -0
- acprof/_bundle/acprof/tui/recovery.py +114 -0
- acprof/_bundle/acprof/tui/recovery_controller.py +169 -0
- acprof/_bundle/acprof/tui/rendering.py +75 -0
- acprof/_bundle/acprof/tui/reports.py +303 -0
- acprof/_bundle/acprof/tui/result_controller.py +279 -0
- acprof/_bundle/acprof/tui/review_inputs.py +54 -0
- acprof/_bundle/acprof/tui/run_form.py +125 -0
- acprof/_bundle/acprof/tui/run_planning.py +97 -0
- acprof/_bundle/acprof/tui/run_results.py +175 -0
- acprof/_bundle/acprof/tui/scrollbar.py +65 -0
- acprof/_bundle/acprof/tui/settings.py +203 -0
- acprof/_bundle/acprof/tui/slash_controller.py +95 -0
- acprof/_bundle/acprof/tui/stages.py +97 -0
- acprof/_bundle/acprof/tui/storage.py +150 -0
- acprof/_bundle/acprof/tui/table.py +213 -0
- acprof/_bundle/acprof/tui/themes.py +112 -0
- acprof/_bundle/acprof/tui/translation_catalog.py +1861 -0
- acprof/_bundle/acprof/tui/tui.tcss +373 -0
- acprof/_bundle/acprof/tui/views.py +706 -0
- acprof/_bundle/acprof/window_records.py +165 -0
- acprof/_bundle/acprof/workloads/__init__.py +222 -0
- acprof/_bundle/acprof/workloads/audio.py +621 -0
- acprof/_bundle/acprof/workloads/contract.py +116 -0
- acprof/_bundle/acprof/workloads/cv.py +240 -0
- acprof/_bundle/acprof/workloads/diffusion.py +410 -0
- acprof/_bundle/acprof/workloads/multimodal.py +329 -0
- acprof/_bundle/acprof/workloads/nlp.py +151 -0
- acprof/_bundle/acprof/workloads/structured.py +151 -0
- acprof/_bundle/acprof/workloads/timeseries.py +69 -0
- acprof/_bundle/assets/audio/librispeech-clean-test-en-30s/audio.wav +0 -0
- acprof/_bundle/assets/audio/librispeech-clean-test-en-30s/source.json +81 -0
- acprof/_bundle/dockerfiles/environment_tools.py +314 -0
- acprof/_bundle/dockerfiles/locks/audio-cpu.artifacts.json +503 -0
- acprof/_bundle/dockerfiles/locks/audio-cpu.txt +143 -0
- acprof/_bundle/dockerfiles/locks/audio-cu124.artifacts.json +600 -0
- acprof/_bundle/dockerfiles/locks/audio-cu124.txt +171 -0
- acprof/_bundle/dockerfiles/locks/audio-cu128.artifacts.json +544 -0
- acprof/_bundle/dockerfiles/locks/audio-cu128.txt +155 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cpu.artifacts.json +510 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cpu.txt +145 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cu124.artifacts.json +607 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cu124.txt +173 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cu128.artifacts.json +642 -0
- acprof/_bundle/dockerfiles/locks/custom-multimodal-cu128.txt +183 -0
- acprof/_bundle/dockerfiles/locks/cv-cpu.artifacts.json +279 -0
- acprof/_bundle/dockerfiles/locks/cv-cpu.txt +79 -0
- acprof/_bundle/dockerfiles/locks/cv-cu124.artifacts.json +376 -0
- acprof/_bundle/dockerfiles/locks/cv-cu124.txt +107 -0
- acprof/_bundle/dockerfiles/locks/cv-cu128.artifacts.json +411 -0
- acprof/_bundle/dockerfiles/locks/cv-cu128.txt +117 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cpu.artifacts.json +335 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cpu.txt +95 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cu124.artifacts.json +432 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cu124.txt +123 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cu128.artifacts.json +467 -0
- acprof/_bundle/dockerfiles/locks/diffusion-cu128.txt +133 -0
- acprof/_bundle/dockerfiles/locks/moss-transformers5104.artifacts.json +621 -0
- acprof/_bundle/dockerfiles/locks/moss-transformers5104.txt +177 -0
- acprof/_bundle/dockerfiles/locks/multimodal-transformers4576.artifacts.json +635 -0
- acprof/_bundle/dockerfiles/locks/multimodal-transformers4576.txt +181 -0
- acprof/_bundle/dockerfiles/locks/nlp-cpu.artifacts.json +363 -0
- acprof/_bundle/dockerfiles/locks/nlp-cpu.txt +103 -0
- acprof/_bundle/dockerfiles/locks/nlp-cu124.artifacts.json +460 -0
- acprof/_bundle/dockerfiles/locks/nlp-cu124.txt +131 -0
- acprof/_bundle/dockerfiles/locks/nlp-cu128.artifacts.json +495 -0
- acprof/_bundle/dockerfiles/locks/nlp-cu128.txt +141 -0
- acprof/_bundle/dockerfiles/locks/onnxruntime-cpu.artifacts.json +261 -0
- acprof/_bundle/dockerfiles/locks/onnxruntime-cpu.txt +73 -0
- acprof/_bundle/dockerfiles/locks/platform-cpu.artifacts.json +99 -0
- acprof/_bundle/dockerfiles/locks/platform-cpu.txt +27 -0
- acprof/_bundle/dockerfiles/locks/platform-cu124.artifacts.json +196 -0
- acprof/_bundle/dockerfiles/locks/platform-cu124.txt +55 -0
- acprof/_bundle/dockerfiles/locks/platform-cu128.artifacts.json +231 -0
- acprof/_bundle/dockerfiles/locks/platform-cu128.txt +65 -0
- acprof/_bundle/dockerfiles/locks/platform-python-cpu.artifacts.json +37 -0
- acprof/_bundle/dockerfiles/locks/platform-python-cpu.txt +9 -0
- acprof/_bundle/dockerfiles/locks/structured-cpu.artifacts.json +279 -0
- acprof/_bundle/dockerfiles/locks/structured-cpu.txt +79 -0
- acprof/_bundle/dockerfiles/locks/structured-cu124.artifacts.json +376 -0
- acprof/_bundle/dockerfiles/locks/structured-cu124.txt +107 -0
- acprof/_bundle/dockerfiles/locks/structured-cu128.artifacts.json +411 -0
- acprof/_bundle/dockerfiles/locks/structured-cu128.txt +117 -0
- acprof/_bundle/dockerfiles/locks/system-trixie-amd64.json +620 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cpu.artifacts.json +314 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cpu.txt +89 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cu124.artifacts.json +411 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cu124.txt +117 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cu128.artifacts.json +446 -0
- acprof/_bundle/dockerfiles/locks/timeseries-cu128.txt +127 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cpu.artifacts.json +594 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cpu.txt +169 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cu124.artifacts.json +691 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cu124.txt +197 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cu128.artifacts.json +726 -0
- acprof/_bundle/dockerfiles/locks/transformers5104-cu128.txt +207 -0
- acprof/_bundle/dockerfiles/platform.Dockerfile +22 -0
- acprof/_bundle/dockerfiles/requirements/audio.in +5 -0
- acprof/_bundle/dockerfiles/requirements/custom-multimodal.in +2 -0
- acprof/_bundle/dockerfiles/requirements/cv.in +4 -0
- acprof/_bundle/dockerfiles/requirements/diffusion.in +6 -0
- acprof/_bundle/dockerfiles/requirements/moss-transformers5104.in +9 -0
- acprof/_bundle/dockerfiles/requirements/multimodal-transformers4576.in +3 -0
- acprof/_bundle/dockerfiles/requirements/nlp.in +7 -0
- acprof/_bundle/dockerfiles/requirements/onnxruntime.in +9 -0
- acprof/_bundle/dockerfiles/requirements/service.in +8 -0
- acprof/_bundle/dockerfiles/requirements/structured.in +4 -0
- acprof/_bundle/dockerfiles/requirements/timeseries.in +2 -0
- acprof/_bundle/dockerfiles/requirements/timm.in +1 -0
- acprof/_bundle/dockerfiles/requirements/transformers5104.in +4 -0
- acprof/_bundle/dockerfiles/runtime-final.Dockerfile +34 -0
- acprof/_bundle/dockerfiles/runtime-model.Dockerfile +19 -0
- acprof/_bundle/dockerfiles/runtime.Dockerfile +23 -0
- acprof/_bundle/examples/coverage/regression.json +17 -0
- acprof/_bundle/examples/hardware-validation.json +19 -0
- acprof/_bundle/examples/multimodal/ultravox.model.json +29 -0
- acprof/_bundle/examples/nlp/smoke.py +111 -0
- acprof/_bundle/examples/onnxruntime/fixtures.py +164 -0
- acprof/_bundle/examples/onnxruntime/iris.json +16 -0
- acprof/_bundle/examples/onnxruntime/iris.model.json +7 -0
- acprof/_bundle/examples/onnxruntime/real_models.py +155 -0
- acprof/_bundle/examples/onnxruntime/smoke.py +68 -0
- acprof/_bundle/examples/structured/chronos_smoke.py +56 -0
- acprof/_bundle/examples/structured/export_models.py +71 -0
- acprof/_bundle/examples/structured/smoke.py +71 -0
- acprof/_bundle/licenses/CC-BY-4.0.txt +156 -0
- acprof/analysis/__init__.py +1 -0
- acprof/analysis/audit.py +276 -0
- acprof/analysis/comparison.py +364 -0
- acprof/analysis/compatibility.py +223 -0
- acprof/analysis/conditions.py +185 -0
- acprof/analysis/independent_comparison.py +191 -0
- acprof/analysis/latency_model.py +1106 -0
- acprof/analysis/latency_report.py +577 -0
- acprof/analysis/model.py +276 -0
- acprof/analysis/precision.py +51 -0
- acprof/analysis/uncertainty.py +144 -0
- acprof/application/__init__.py +1 -0
- acprof/application/download.py +14 -0
- acprof/application/errors.py +5 -0
- acprof/application/notifications.py +302 -0
- acprof/application/preparation.py +333 -0
- acprof/application/resources.py +66 -0
- acprof/application/run.py +462 -0
- acprof/application/terminal_log.py +136 -0
- acprof/artifact_layout.py +261 -0
- acprof/artifacts.py +161 -0
- acprof/capabilities.py +404 -0
- acprof/cli/__init__.py +1 -0
- acprof/cli/audit.py +74 -0
- acprof/cli/auto.py +41 -0
- acprof/cli/backfill_compute.py +498 -0
- acprof/cli/compare.py +34 -0
- acprof/cli/coverage.py +70 -0
- acprof/cli/doctor.py +47 -0
- acprof/cli/inspect.py +101 -0
- acprof/cli/load.py +190 -0
- acprof/cli/main.py +64 -0
- acprof/cli/model_store.py +22 -0
- acprof/cli/plot.py +211 -0
- acprof/cli/posthoc.py +113 -0
- acprof/cli/probe.py +186 -0
- acprof/cli/report.py +35 -0
- acprof/cli/results.py +37 -0
- acprof/cli/run.py +54 -0
- acprof/cli/stats.py +94 -0
- acprof/cli/tui.py +65 -0
- acprof/config.py +177 -0
- acprof/container/__init__.py +1 -0
- acprof/container/compat/__init__.py +1 -0
- acprof/container/compat/transformers_dynamic.py +54 -0
- acprof/container/compat/transformers_model_types.json +1091 -0
- acprof/container/compute_profile_runner.py +392 -0
- acprof/container/download_model.py +165 -0
- acprof/container/execution.py +73 -0
- acprof/container/handlers/__init__.py +298 -0
- acprof/container/handlers/audio.py +743 -0
- acprof/container/handlers/custom_pipeline.py +74 -0
- acprof/container/handlers/cv.py +321 -0
- acprof/container/handlers/diffusion.py +728 -0
- acprof/container/handlers/moss.py +64 -0
- acprof/container/handlers/multimodal.py +609 -0
- acprof/container/handlers/nlp.py +747 -0
- acprof/container/handlers/onnx_tasks.py +231 -0
- acprof/container/handlers/onnxruntime.py +90 -0
- acprof/container/handlers/structured.py +185 -0
- acprof/container/handlers/timeseries.py +178 -0
- acprof/container/load_policy.py +81 -0
- acprof/container/loading_quality.py +48 -0
- acprof/container/local_pipeline.py +31 -0
- acprof/container/model_files.py +280 -0
- acprof/container/model_probe.py +80 -0
- acprof/container/onnx_session.py +150 -0
- acprof/container/runtime_manifest.py +150 -0
- acprof/container/runtime_validate.py +156 -0
- acprof/container/server.py +209 -0
- acprof/container/torch_execution.py +55 -0
- acprof/container/validation.py +172 -0
- acprof/cpu_affinity.py +35 -0
- acprof/dependency_locks.py +129 -0
- acprof/experiment.py +356 -0
- acprof/extensions/__init__.py +294 -0
- acprof/extensions/builtin/manifest.json +3591 -0
- acprof/extensions/onnxruntime/manifest.json +191 -0
- acprof/extensions/schema.py +238 -0
- acprof/extensions/transformers/4.57.6.json +1639 -0
- acprof/extensions/transformers/5.10.4.json +1812 -0
- acprof/failures.py +115 -0
- acprof/hardware_conditions.py +18 -0
- acprof/hf_download.py +143 -0
- acprof/hf_endpoints.py +62 -0
- acprof/hf_transport.py +238 -0
- acprof/host/__init__.py +1 -0
- acprof/host/automation.py +161 -0
- acprof/host/client.py +520 -0
- acprof/host/collection_history.py +77 -0
- acprof/host/collection_workflow.py +285 -0
- acprof/host/command.py +119 -0
- acprof/host/container_lifecycle.py +225 -0
- acprof/host/container_state.py +128 -0
- acprof/host/coverage_state.py +259 -0
- acprof/host/detect.py +669 -0
- acprof/host/docker_runtime.py +359 -0
- acprof/host/doctor.py +158 -0
- acprof/host/env_utils.py +233 -0
- acprof/host/execution_conditions.py +87 -0
- acprof/host/gpu_device.py +66 -0
- acprof/host/hardware_conditions.py +130 -0
- acprof/host/images/__init__.py +1 -0
- acprof/host/images/dependencies.py +169 -0
- acprof/host/images/environment.py +313 -0
- acprof/host/images/graph.py +143 -0
- acprof/host/images/identity.py +6 -0
- acprof/host/images/inventory.py +445 -0
- acprof/host/images/runtime.py +457 -0
- acprof/host/images/scan.py +154 -0
- acprof/host/input_plan.py +1056 -0
- acprof/host/interface_probe.py +141 -0
- acprof/host/largest_scale_probe.py +566 -0
- acprof/host/matrix_plan.py +98 -0
- acprof/host/measurement/__init__.py +1 -0
- acprof/host/measurement/boundary.py +162 -0
- acprof/host/measurement/capture.py +139 -0
- acprof/host/measurement/checkpoint.py +275 -0
- acprof/host/measurement/config.py +212 -0
- acprof/host/measurement/diagnostics.py +247 -0
- acprof/host/measurement/energy_validation.py +172 -0
- acprof/host/measurement/errors.py +25 -0
- acprof/host/measurement/execution.py +396 -0
- acprof/host/measurement/load.py +237 -0
- acprof/host/measurement/metrics.py +975 -0
- acprof/host/measurement/monitors.py +143 -0
- acprof/host/measurement/publication.py +171 -0
- acprof/host/measurement/requests.py +97 -0
- acprof/host/measurement/types.py +82 -0
- acprof/host/model_coverage.py +337 -0
- acprof/host/model_errors.py +116 -0
- acprof/host/model_inspection.py +107 -0
- acprof/host/model_schema.py +35 -0
- acprof/host/model_store.py +733 -0
- acprof/host/network_preflight.py +229 -0
- acprof/host/orchestrator.py +1133 -0
- acprof/host/permissions.py +117 -0
- acprof/host/platform_metadata.py +63 -0
- acprof/host/posthoc/__init__.py +12 -0
- acprof/host/posthoc/backfill.py +415 -0
- acprof/host/posthoc/context.py +400 -0
- acprof/host/posthoc/plans.py +597 -0
- acprof/host/posthoc/service.py +303 -0
- acprof/host/posthoc/storage.py +491 -0
- acprof/host/preflight.py +370 -0
- acprof/host/profiling/__init__.py +1 -0
- acprof/host/profiling/compute.py +436 -0
- acprof/host/profiling/compute_plan.py +261 -0
- acprof/host/profiling/execution.py +547 -0
- acprof/host/profiling/execution_plan.py +576 -0
- acprof/host/profiling/progress.py +83 -0
- acprof/host/profiling/support.py +252 -0
- acprof/host/profiling/tools/__init__.py +1 -0
- acprof/host/profiling/tools/advisor.py +164 -0
- acprof/host/profiling/tools/compute_parsers.py +409 -0
- acprof/host/profiling/tools/execution_environment.py +135 -0
- acprof/host/profiling/tools/execution_parsers.py +364 -0
- acprof/host/profiling/tools/massif.py +418 -0
- acprof/host/profiling/tools/ncu.py +696 -0
- acprof/host/profiling/tools/nsys.py +326 -0
- acprof/host/profiling/tools/tool_discovery.py +229 -0
- acprof/host/profiling/tools/torch.py +158 -0
- acprof/host/result_cleanup.py +65 -0
- acprof/host/run_state.py +639 -0
- acprof/host/runtime_settings.py +10 -0
- acprof/host/runtime_validation.py +302 -0
- acprof/host/source_bundle.py +96 -0
- acprof/host/startup_probe.py +95 -0
- acprof/host/static_metadata.py +936 -0
- acprof/host/task_support.py +121 -0
- acprof/installation.py +26 -0
- acprof/latency_slo.py +62 -0
- acprof/messages.py +26 -0
- acprof/metric_registry.py +315 -0
- acprof/model_candidates.py +297 -0
- acprof/model_contract.py +234 -0
- acprof/model_dependencies.py +93 -0
- acprof/model_dependency_domain.py +45 -0
- acprof/model_dependency_eval.py +438 -0
- acprof/model_dependency_flow.py +200 -0
- acprof/model_evidence.py +115 -0
- acprof/model_metadata_analysis.py +156 -0
- acprof/model_repository.py +149 -0
- acprof/model_resolution.py +129 -0
- acprof/model_review.py +155 -0
- acprof/model_source_analysis.py +299 -0
- acprof/model_spec.py +204 -0
- acprof/model_support.py +105 -0
- acprof/model_transforms.py +81 -0
- acprof/monitors/__init__.py +1 -0
- acprof/monitors/common.py +63 -0
- acprof/monitors/energy_cpu.py +651 -0
- acprof/monitors/energy_nvml.py +381 -0
- acprof/monitors/gpu_metrics.py +123 -0
- acprof/monitors/interfaces.py +74 -0
- acprof/monitors/perf_mips.py +617 -0
- acprof/monitors/rapl_topology.py +100 -0
- acprof/monitors/resource_metrics.py +472 -0
- acprof/monitors/resource_readers.py +435 -0
- acprof/monitors/resource_usage.py +450 -0
- acprof/monitors/sampling.py +79 -0
- acprof/network_policy.py +182 -0
- acprof/notifications.py +279 -0
- acprof/packet/__init__.py +1 -0
- acprof/packet/merge_packet_latency.py +369 -0
- acprof/packet/sniff_parse_pcap.py +240 -0
- acprof/pixel_metrics.py +103 -0
- acprof/platform.py +180 -0
- acprof/plotting/__init__.py +1 -0
- acprof/plotting/config.py +487 -0
- acprof/plotting/data.py +236 -0
- acprof/plotting/diagnostics.py +747 -0
- acprof/plotting/latency.py +638 -0
- acprof/plotting/metrics.py +648 -0
- acprof/plotting/plotly-LICENSE.txt +21 -0
- acprof/plotting/report.css +61 -0
- acprof/plotting/report.html +68 -0
- acprof/plotting/report.js +295 -0
- acprof/plotting/report.py +66 -0
- acprof/plotting/styles.py +111 -0
- acprof/plotting/view_model.js +145 -0
- acprof/precision.py +42 -0
- acprof/preparation_events.py +78 -0
- acprof/progress_events.py +105 -0
- acprof/quality.py +153 -0
- acprof/resource_budget.py +29 -0
- acprof/result_csv.py +263 -0
- acprof/result_layers.py +365 -0
- acprof/run_args.py +223 -0
- acprof/run_options.py +225 -0
- acprof/runtime_dependencies.py +195 -0
- acprof/runtime_profiles.py +258 -0
- acprof/runtime_settings.py +72 -0
- acprof/source_identity.py +68 -0
- acprof/telemetry.py +225 -0
- acprof/testing/__init__.py +1 -0
- acprof/testing/evidence.py +117 -0
- acprof/testing/plugin.py +126 -0
- acprof/testing/provenance.py +53 -0
- acprof/testing/sharding.py +14 -0
- acprof/tui/__init__.py +1 -0
- acprof/tui/app.py +934 -0
- acprof/tui/app_contracts.py +43 -0
- acprof/tui/catalog_controller.py +120 -0
- acprof/tui/commands.py +209 -0
- acprof/tui/configuration_controller.py +436 -0
- acprof/tui/diagnostics.py +340 -0
- acprof/tui/downloads.py +56 -0
- acprof/tui/environment.py +248 -0
- acprof/tui/experiment_catalog.py +279 -0
- acprof/tui/experiment_controller.py +132 -0
- acprof/tui/experiment_picker.py +273 -0
- acprof/tui/field_validation.py +62 -0
- acprof/tui/i18n.py +57 -0
- acprof/tui/image_controller.py +525 -0
- acprof/tui/images.py +737 -0
- acprof/tui/input.py +191 -0
- acprof/tui/localization_controller.py +110 -0
- acprof/tui/log.py +310 -0
- acprof/tui/model_candidates.py +278 -0
- acprof/tui/model_controller.py +111 -0
- acprof/tui/model_input.py +12 -0
- acprof/tui/model_store.py +166 -0
- acprof/tui/preflight_controller.py +271 -0
- acprof/tui/preparation.py +269 -0
- acprof/tui/presentation.py +46 -0
- acprof/tui/process.py +111 -0
- acprof/tui/process_controller.py +218 -0
- acprof/tui/profile_controller.py +109 -0
- acprof/tui/progress.py +195 -0
- acprof/tui/recovery.py +114 -0
- acprof/tui/recovery_controller.py +169 -0
- acprof/tui/rendering.py +75 -0
- acprof/tui/reports.py +303 -0
- acprof/tui/result_controller.py +279 -0
- acprof/tui/review_inputs.py +54 -0
- acprof/tui/run_form.py +125 -0
- acprof/tui/run_planning.py +97 -0
- acprof/tui/run_results.py +175 -0
- acprof/tui/scrollbar.py +65 -0
- acprof/tui/settings.py +203 -0
- acprof/tui/slash_controller.py +95 -0
- acprof/tui/stages.py +97 -0
- acprof/tui/storage.py +150 -0
- acprof/tui/table.py +213 -0
- acprof/tui/themes.py +112 -0
- acprof/tui/translation_catalog.py +1861 -0
- acprof/tui/tui.tcss +373 -0
- acprof/tui/views.py +706 -0
- acprof/window_records.py +165 -0
- acprof/workloads/__init__.py +222 -0
- acprof/workloads/audio.py +621 -0
- acprof/workloads/contract.py +116 -0
- acprof/workloads/cv.py +240 -0
- acprof/workloads/diffusion.py +410 -0
- acprof/workloads/multimodal.py +329 -0
- acprof/workloads/nlp.py +151 -0
- acprof/workloads/structured.py +151 -0
- acprof/workloads/timeseries.py +69 -0
- acprof-0.3.0.dist-info/METADATA +117 -0
- acprof-0.3.0.dist-info/RECORD +704 -0
- acprof-0.3.0.dist-info/WHEEL +4 -0
- acprof-0.3.0.dist-info/entry_points.txt +2 -0
- acprof-0.3.0.dist-info/licenses/LICENSE +202 -0
- acprof-0.3.0.dist-info/licenses/NOTICE +43 -0
- acprof-0.3.0.dist-info/licenses/licenses/CC-BY-4.0.txt +156 -0
acprof/host/run_state.py
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
"""主实验状态、目录互斥与 case 恢复;所有操作均在测量窗口之外。"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import stat
|
|
13
|
+
from dataclasses import asdict
|
|
14
|
+
from datetime import UTC, datetime
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import TextIO
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
|
|
19
|
+
from acprof.artifact_layout import ArtifactLayout
|
|
20
|
+
from acprof.artifacts import atomic_write_json, file_sha256, replace_file_durably, sync_directory
|
|
21
|
+
from acprof.host.execution_conditions import measurement_environment
|
|
22
|
+
from acprof.messages import Message, join_messages, message
|
|
23
|
+
from acprof.platform import detect_environment
|
|
24
|
+
from acprof.result_csv import expected_measurements, read_result_csv
|
|
25
|
+
from acprof.run_args import flatten_run_options
|
|
26
|
+
from acprof.source_identity import measurement_sources, source_fingerprint
|
|
27
|
+
|
|
28
|
+
RUN_STATE_NAME = "run_state.json"
|
|
29
|
+
RESULT_LOCK_NAME = ".acprof-result.lock"
|
|
30
|
+
MAX_RUN_STATE_BYTES = 4 * 1024 * 1024
|
|
31
|
+
# Native Linux only. Never derive this machine-wide per-user namespace from TMPDIR.
|
|
32
|
+
# Test runners inject an isolated directory in-process, not via a production env option.
|
|
33
|
+
MEASUREMENT_LOCK_ROOT = Path("/tmp")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _sync_directory_chain(directory: Path, root: Path) -> None:
|
|
37
|
+
"""Persist recovery-directory entries from a copied file up to the experiment root."""
|
|
38
|
+
directory = directory.absolute()
|
|
39
|
+
root = root.absolute()
|
|
40
|
+
if not directory.is_relative_to(root):
|
|
41
|
+
raise ValueError(f"Recovery directory escapes experiment root: {directory}")
|
|
42
|
+
while True:
|
|
43
|
+
sync_directory(directory)
|
|
44
|
+
if directory == root:
|
|
45
|
+
return
|
|
46
|
+
directory = directory.parent
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RunStateError(RuntimeError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _validate_lock_descriptor(descriptor: int, *, label: str) -> None:
|
|
54
|
+
info = os.fstat(descriptor)
|
|
55
|
+
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or info.st_nlink != 1:
|
|
56
|
+
raise OSError(f"{label} lock is not a regular file owned only by this user")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def check_result_directory_idle(directory: str | Path) -> None:
|
|
60
|
+
"""Fail closed when an existing result-directory lock is active or malformed."""
|
|
61
|
+
import fcntl
|
|
62
|
+
|
|
63
|
+
path = ArtifactLayout.discover(directory).path(RESULT_LOCK_NAME)
|
|
64
|
+
try:
|
|
65
|
+
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
return
|
|
68
|
+
except OSError as exc:
|
|
69
|
+
raise RunStateError(f"无法检查结果目录锁;锁文件不可用:{path} ({exc})") from exc
|
|
70
|
+
try:
|
|
71
|
+
_validate_lock_descriptor(descriptor, label="result")
|
|
72
|
+
fcntl.flock(descriptor, fcntl.LOCK_SH | fcntl.LOCK_NB)
|
|
73
|
+
except BlockingIOError as exc:
|
|
74
|
+
raise RunStateError(message("原实验仍有进程写入,请等待其退出后再恢复。")) from exc
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
raise RunStateError(f"无法检查结果目录锁;锁文件不可用:{path} ({exc})") from exc
|
|
77
|
+
finally:
|
|
78
|
+
os.close(descriptor)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _finite_json_number(raw: str) -> float:
|
|
82
|
+
value = float(raw)
|
|
83
|
+
if not math.isfinite(value):
|
|
84
|
+
raise ValueError(message("run_state.json 包含非有限数值"))
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def utc_now() -> str:
|
|
89
|
+
return datetime.now(UTC).isoformat()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def host_identity(project_dir: str | Path) -> dict:
|
|
93
|
+
source_hash = source_fingerprint(project_dir, measurement_sources(project_dir),
|
|
94
|
+
scope="measurement-source-v2")
|
|
95
|
+
packages = sorted((dist.metadata["Name"] or "", dist.version)
|
|
96
|
+
for dist in importlib.metadata.distributions())
|
|
97
|
+
machine_id = Path("/etc/machine-id")
|
|
98
|
+
return {
|
|
99
|
+
**detect_environment().metadata(),
|
|
100
|
+
"machine": platform.machine(), "kernel": platform.release(), "hostname": platform.node(),
|
|
101
|
+
"machine_id_sha256": file_sha256(machine_id) if machine_id.is_file() else None,
|
|
102
|
+
"python": platform.python_version(), "source_sha256": source_hash,
|
|
103
|
+
"packages_sha256": hashlib.sha256(json.dumps(packages).encode()).hexdigest(),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run_options(args) -> dict:
|
|
108
|
+
from acprof.host.gpu_device import selected_gpu_device
|
|
109
|
+
ignored = {"resume", "rebuild_image", "output_dir", "notify"}
|
|
110
|
+
options = {name: value for name, value in flatten_run_options(vars(args)).items() if name not in ignored}
|
|
111
|
+
# Freeze even default policies: they affect both timing and retained evidence.
|
|
112
|
+
options.setdefault("sampling_policy", "burst")
|
|
113
|
+
options.setdefault("raw_sample_limit", 100000)
|
|
114
|
+
from acprof.model_repository import model_source
|
|
115
|
+
source = model_source(options.get("model_source"))
|
|
116
|
+
if source == "huggingface":
|
|
117
|
+
options.pop("model_source", None) # Preserve historical HF resume options.
|
|
118
|
+
else:
|
|
119
|
+
options["model_source"] = source
|
|
120
|
+
if options.get("revision") is None:
|
|
121
|
+
options.pop("revision", None)
|
|
122
|
+
if options.get("input_scale_policy", "auto") == "auto":
|
|
123
|
+
options.pop("input_scale_policy", None)
|
|
124
|
+
device = selected_gpu_device()
|
|
125
|
+
if device:
|
|
126
|
+
options["gpu_device"] = device["uuid"]
|
|
127
|
+
elif options.get("gpu_device") is None:
|
|
128
|
+
options.pop("gpu_device", None)
|
|
129
|
+
from acprof.cpu_affinity import normalize_cpu_set
|
|
130
|
+
cpuset = normalize_cpu_set(options.get("cpuset_cpus", ""))
|
|
131
|
+
if cpuset:
|
|
132
|
+
options["cpuset_cpus"] = cpuset
|
|
133
|
+
else:
|
|
134
|
+
options.pop("cpuset_cpus", None)
|
|
135
|
+
for name in ("cpus", "mems", "gpus"):
|
|
136
|
+
options[name] = ",".join(part.strip().lower() for part in options[name].split(",") if part.strip())
|
|
137
|
+
# New options only enter pre-execution identity when explicitly requested.
|
|
138
|
+
# Absent defaults retain the old serialized options; no observed values enter here.
|
|
139
|
+
options["measurement_environment"] = measurement_environment()
|
|
140
|
+
if options.get("workload_spec"):
|
|
141
|
+
path = Path(options["workload_spec"]).expanduser().resolve()
|
|
142
|
+
options["workload_spec"] = str(path)
|
|
143
|
+
options["workload_spec_sha256"] = file_sha256(path)
|
|
144
|
+
if options.get("model_spec"):
|
|
145
|
+
path = Path(options["model_spec"]).expanduser().resolve()
|
|
146
|
+
options["model_spec"] = str(path)
|
|
147
|
+
options["model_spec_sha256"] = file_sha256(path)
|
|
148
|
+
else:
|
|
149
|
+
options.pop("model_spec", None)
|
|
150
|
+
return options
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def load_run_state(directory: str | Path) -> dict:
|
|
154
|
+
try:
|
|
155
|
+
layout = ArtifactLayout.discover(directory)
|
|
156
|
+
path = layout.path(RUN_STATE_NAME)
|
|
157
|
+
with path.open("rb") as stream:
|
|
158
|
+
content = stream.read(MAX_RUN_STATE_BYTES + 1)
|
|
159
|
+
if len(content) > MAX_RUN_STATE_BYTES:
|
|
160
|
+
raise ValueError(message("run_state.json 超过 4 MiB 读取上限"))
|
|
161
|
+
try:
|
|
162
|
+
payload = json.loads(content.decode("utf-8"), parse_float=_finite_json_number,
|
|
163
|
+
parse_constant=_finite_json_number)
|
|
164
|
+
except json.JSONDecodeError as exc:
|
|
165
|
+
raise ValueError(message("run_state.json 的 JSON 格式无效")) from exc
|
|
166
|
+
except (OSError, UnicodeError, ValueError, RecursionError) as exc:
|
|
167
|
+
reason = exc.args[0] if exc.args and isinstance(exc.args[0], Message) else str(exc)
|
|
168
|
+
raise RunStateError(message(
|
|
169
|
+
"无法读取恢复状态 {0};历史实验请使用新输出目录:{1}", directory, reason,
|
|
170
|
+
)) from exc
|
|
171
|
+
if not isinstance(payload, dict) or payload.get("schema_version") != 1:
|
|
172
|
+
raise RunStateError(message("不支持的主实验状态格式:{0}", path))
|
|
173
|
+
if payload.get("layout_version", 1) != layout.layout_version:
|
|
174
|
+
raise RunStateError(message("恢复状态与产物布局不一致:{0}", path))
|
|
175
|
+
return payload
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _expected(data: dict, cpu=None, mem=None, gpu=None):
|
|
179
|
+
options = data["options"]
|
|
180
|
+
return expected_measurements(
|
|
181
|
+
[cpu] if cpu is not None else [int(x) for x in options["cpus"].split(",")],
|
|
182
|
+
[mem] if mem is not None else [int(x) for x in options["mems"].split(",")],
|
|
183
|
+
[gpu] if gpu is not None else options["gpus"].split(","),
|
|
184
|
+
data["runtime"]["planned"]["scales"], options["warmup"], options["repeat"],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def recovery_phase(directory: str | Path, data: dict) -> str:
|
|
189
|
+
layout = ArtifactLayout.discover(directory)
|
|
190
|
+
if data.get("status") == "complete":
|
|
191
|
+
return "complete"
|
|
192
|
+
if (data.get("runtime") or data.get("cases") or layout.result_csv.exists()
|
|
193
|
+
or any(layout.root.glob("result_case_*.csv"))
|
|
194
|
+
or any((layout.root / ".acprof/work/cases").glob("*/result.csv"))):
|
|
195
|
+
return "measurement"
|
|
196
|
+
return "preparation"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def validate_resume(directory: str | Path, data: dict, *, project_dir: str | Path,
|
|
200
|
+
options: dict | None = None) -> str:
|
|
201
|
+
"""Read-only checks shared by recovery review and the locked writer.
|
|
202
|
+
|
|
203
|
+
Image availability and live hardware preflight remain the runner's responsibility.
|
|
204
|
+
Returning a phase never grants permission to mix changed measurement identities.
|
|
205
|
+
"""
|
|
206
|
+
layout = ArtifactLayout.discover(directory)
|
|
207
|
+
issues = []
|
|
208
|
+
if not isinstance(data.get("options"), dict) or not isinstance(data.get("host"), dict):
|
|
209
|
+
raise RunStateError(message("原实验缺少完整的参数或主机身份,无法续跑。"))
|
|
210
|
+
if options is not None:
|
|
211
|
+
previous = {"profiling_mode": "full", **flatten_run_options(data["options"])}
|
|
212
|
+
current = {"profiling_mode": "full", **flatten_run_options(options)}
|
|
213
|
+
changed = sorted(name for name in previous.keys() | current.keys()
|
|
214
|
+
if (name in previous) != (name in current) or previous.get(name) != current.get(name))
|
|
215
|
+
if changed:
|
|
216
|
+
issues.append(message("实验参数已变化:{0}", ", ".join(changed)))
|
|
217
|
+
previous_host, current_host = data["host"], host_identity(project_dir)
|
|
218
|
+
labels = {"source_sha256": "AC-Prof 采集源码", "packages_sha256": "Python 依赖",
|
|
219
|
+
"python": "Python 版本"}
|
|
220
|
+
for name in sorted(previous_host.keys() | current_host.keys()):
|
|
221
|
+
if previous_host.get(name) != current_host.get(name):
|
|
222
|
+
issues.append(message("{0}已变化", message(labels.get(name, "主机身份({0})"), name)))
|
|
223
|
+
if issues:
|
|
224
|
+
raise RunStateError(message("无法续跑;请使用新实验保留原结果。\n{0}", join_messages("\n", issues)))
|
|
225
|
+
if not isinstance(data.get("cases", {}), dict) or not isinstance(data.get("artifacts", {}), dict):
|
|
226
|
+
raise RunStateError(message("原实验的 case 或产物记录无效。"))
|
|
227
|
+
phase = recovery_phase(directory, data)
|
|
228
|
+
complete = phase == "complete"
|
|
229
|
+
if not data.get("runtime") and phase != "preparation":
|
|
230
|
+
raise RunStateError(message("已有测量证据但缺少冻结运行环境,不能按准备失败重试。"))
|
|
231
|
+
# Completed CSVs may have documented posthoc changes; preserve that contract.
|
|
232
|
+
if complete:
|
|
233
|
+
read_result_csv(layout.result_csv, expected=_expected(data))
|
|
234
|
+
for name, digest in data.get("artifacts", {}).items():
|
|
235
|
+
if complete and Path(name).name not in {"matrix_plan.json", "startup_oom_pruning.json"}:
|
|
236
|
+
continue
|
|
237
|
+
path = layout.contained(name)
|
|
238
|
+
if not path.is_file() or file_sha256(path) != digest:
|
|
239
|
+
raise RunStateError(message("恢复产物缺失或已改变:{0};请恢复原文件或创建新实验。", path))
|
|
240
|
+
if not complete:
|
|
241
|
+
for name, record in data.get("cases", {}).items():
|
|
242
|
+
if not isinstance(record, dict):
|
|
243
|
+
raise RunStateError(message("原实验的 case 或产物记录无效。"))
|
|
244
|
+
if record.get("status") == "complete":
|
|
245
|
+
path = layout.contained(name)
|
|
246
|
+
if not path.is_file() or file_sha256(path) != record.get("sha256"):
|
|
247
|
+
raise RunStateError(message("已完成 case 的 CSV 缺失或改变:{0}", path))
|
|
248
|
+
elif record.get("measurement_status") == "complete":
|
|
249
|
+
from acprof.host.measurement.checkpoint import read_measurement_checkpoint
|
|
250
|
+
case = layout.case_from_csv(layout.contained(name))
|
|
251
|
+
cpu, mem, gpu = record["resources"]
|
|
252
|
+
try:
|
|
253
|
+
read_measurement_checkpoint(case, record["measurement_checkpoint"],
|
|
254
|
+
run_id=data["run_id"], attempt_id=record["case_attempt_id"],
|
|
255
|
+
expected=_expected(data, cpu, mem, gpu))
|
|
256
|
+
except (OSError, ValueError, KeyError) as exc:
|
|
257
|
+
raise RunStateError(f"已完成测量的原始证据无效:{case.case_id}: {exc}") from exc
|
|
258
|
+
return phase
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ResultDirectoryLock:
|
|
262
|
+
"""Linux advisory lock: process exit releases ownership without stale PID recovery."""
|
|
263
|
+
def __init__(self, directory: str | Path, *, new: bool = False):
|
|
264
|
+
layout = ArtifactLayout.for_new_run(directory) if new else ArtifactLayout.discover(directory)
|
|
265
|
+
self.path = layout.path(RESULT_LOCK_NAME)
|
|
266
|
+
self.stream: TextIO | None = None
|
|
267
|
+
|
|
268
|
+
def __enter__(self):
|
|
269
|
+
import fcntl
|
|
270
|
+
|
|
271
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
try:
|
|
273
|
+
descriptor = os.open(
|
|
274
|
+
self.path,
|
|
275
|
+
os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW | os.O_CLOEXEC,
|
|
276
|
+
0o600,
|
|
277
|
+
)
|
|
278
|
+
self.stream = os.fdopen(descriptor, "a+")
|
|
279
|
+
_validate_lock_descriptor(self.stream.fileno(), label="result")
|
|
280
|
+
fcntl.flock(self.stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
281
|
+
except OSError as exc:
|
|
282
|
+
self.__exit__()
|
|
283
|
+
raise RunStateError(
|
|
284
|
+
"无法取得结果目录锁;另一个采集或补采进程可能正在使用结果目录,"
|
|
285
|
+
f"或锁文件不可用:{self.path} ({exc})"
|
|
286
|
+
) from exc
|
|
287
|
+
return self
|
|
288
|
+
|
|
289
|
+
def __exit__(self, *_args):
|
|
290
|
+
if self.stream is not None:
|
|
291
|
+
self.stream.close()
|
|
292
|
+
self.stream = None
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class MeasurementLock(ResultDirectoryLock):
|
|
296
|
+
"""同机同用户的实验串行化,防止跨结果目录共享端口及采样资源。"""
|
|
297
|
+
def __init__(self):
|
|
298
|
+
self.path = MEASUREMENT_LOCK_ROOT / f"acprof-measurement-{os.getuid()}.lock"
|
|
299
|
+
self.stream = None
|
|
300
|
+
|
|
301
|
+
def __enter__(self):
|
|
302
|
+
import fcntl
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
descriptor = os.open(self.path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600)
|
|
306
|
+
self.stream = os.fdopen(descriptor, "a+")
|
|
307
|
+
_validate_lock_descriptor(self.stream.fileno(), label="measurement")
|
|
308
|
+
fcntl.flock(self.stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
309
|
+
except OSError as error:
|
|
310
|
+
self.__exit__()
|
|
311
|
+
raise RunStateError(
|
|
312
|
+
"无法取得本机测量锁;同一用户可能已有 AC-Prof 采集或补采运行,"
|
|
313
|
+
f"或锁文件不可用:{self.path} ({error})"
|
|
314
|
+
) from error
|
|
315
|
+
return self
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class RunState:
|
|
319
|
+
def __init__(self, directory: str | Path, options: dict, *, resume: bool, project_dir: str,
|
|
320
|
+
preparation_artifacts: dict[str, str] | None = None):
|
|
321
|
+
self.directory = Path(directory).resolve()
|
|
322
|
+
self.layout = ArtifactLayout.discover(self.directory) if resume else ArtifactLayout.for_new_run(self.directory)
|
|
323
|
+
self.path = self.layout.path(RUN_STATE_NAME)
|
|
324
|
+
self.lock = ResultDirectoryLock(self.directory, new=not resume)
|
|
325
|
+
self.measurement_lock = MeasurementLock()
|
|
326
|
+
self.measurement_lock.__enter__()
|
|
327
|
+
try:
|
|
328
|
+
self.lock.__enter__()
|
|
329
|
+
attempt = {"started_at": utc_now(), "pid": os.getpid(), "resume": resume}
|
|
330
|
+
if resume:
|
|
331
|
+
self.data = load_run_state(self.directory)
|
|
332
|
+
phase = validate_resume(self.directory, self.data, project_dir=project_dir, options=options)
|
|
333
|
+
if phase == "preparation":
|
|
334
|
+
attempt["preparation_backup"] = self._archive_preparation()
|
|
335
|
+
self.data["status"] = "preparing"
|
|
336
|
+
else:
|
|
337
|
+
prepared = preparation_artifacts or {}
|
|
338
|
+
allowed_prepared = {str(self.layout.path(name).relative_to(self.directory))
|
|
339
|
+
for name in ("auto_report.json", "model_resolution.json")}
|
|
340
|
+
if set(prepared) - allowed_prepared:
|
|
341
|
+
raise RunStateError("自动准备产物包含不允许的文件")
|
|
342
|
+
for name, digest in prepared.items():
|
|
343
|
+
path = self.layout.contained(name)
|
|
344
|
+
if not path.is_file() or file_sha256(path) != digest:
|
|
345
|
+
raise RunStateError(f"自动准备产物缺失或已改变:{path}")
|
|
346
|
+
try:
|
|
347
|
+
self.layout.initialize(allowed_files=prepared)
|
|
348
|
+
except ValueError as exc:
|
|
349
|
+
raise RunStateError(str(exc)) from exc
|
|
350
|
+
self.data = {"schema_version": 1, "layout_version": 2, "run_id": uuid4().hex, "created_at": utc_now(),
|
|
351
|
+
"status": "preparing", "options": options, "host": host_identity(project_dir),
|
|
352
|
+
"cases": {}, "artifacts": {}, "attempts": []}
|
|
353
|
+
self.data["attempts"].append(attempt)
|
|
354
|
+
self.save()
|
|
355
|
+
except BaseException:
|
|
356
|
+
self.lock.__exit__(None, None, None)
|
|
357
|
+
self.measurement_lock.__exit__(None, None, None)
|
|
358
|
+
raise
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def ready(self) -> bool:
|
|
362
|
+
return "runtime" in self.data
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def complete(self) -> bool:
|
|
366
|
+
return self.data.get("status") == "complete"
|
|
367
|
+
|
|
368
|
+
def save(self) -> None:
|
|
369
|
+
self.data["updated_at"] = utc_now()
|
|
370
|
+
atomic_write_json(self.path, self.data)
|
|
371
|
+
|
|
372
|
+
def artifact_path(self, relative: str) -> Path:
|
|
373
|
+
try:
|
|
374
|
+
return self.layout.contained(relative)
|
|
375
|
+
except ValueError as exc:
|
|
376
|
+
raise RunStateError(str(exc)) from exc
|
|
377
|
+
|
|
378
|
+
def _archive_preparation(self) -> str:
|
|
379
|
+
"""Copy preparation evidence under the writer lock before any retry overwrites it."""
|
|
380
|
+
names = ("run_state.json", "auto_report.json", "model_resolution.json",
|
|
381
|
+
"interface_validation.json", "runtime_validation.json", "static_meta.json",
|
|
382
|
+
"capability_report.json", "collection_history.json", "input_scale_plan.json",
|
|
383
|
+
"compute_profile_plan.json", "execution_profile_plan.json")
|
|
384
|
+
sources = [self.layout.path(name) for name in names]
|
|
385
|
+
for name in ("logs", "compute_profiles", "execution_profiles"):
|
|
386
|
+
sources.extend(self.layout.path(name).rglob("*"))
|
|
387
|
+
backup = self.layout.path("interrupted_cases").parent / "preparation_attempts" / uuid4().hex
|
|
388
|
+
backup_directories: set[Path] = set()
|
|
389
|
+
for source in sources:
|
|
390
|
+
source = self.artifact_path(str(source.relative_to(self.directory)))
|
|
391
|
+
if not source.exists():
|
|
392
|
+
continue
|
|
393
|
+
if source.is_dir():
|
|
394
|
+
continue
|
|
395
|
+
if not source.is_file():
|
|
396
|
+
raise RunStateError(message("不支持的准备产物类型:{0}", source))
|
|
397
|
+
target = backup / source.relative_to(self.directory)
|
|
398
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
399
|
+
shutil.copy2(source, target)
|
|
400
|
+
with target.open("rb") as stream:
|
|
401
|
+
os.fsync(stream.fileno())
|
|
402
|
+
backup_directories.add(target.parent)
|
|
403
|
+
for directory in sorted(backup_directories, key=lambda path: len(path.parts), reverse=True):
|
|
404
|
+
_sync_directory_chain(directory, self.directory)
|
|
405
|
+
return str(backup.relative_to(self.directory))
|
|
406
|
+
|
|
407
|
+
def bind_matrix_plan(self, plan: dict) -> None:
|
|
408
|
+
from acprof.host.matrix_plan import MATRIX_PLAN_NAME
|
|
409
|
+
from acprof.host.startup_probe import PROBE_NAME
|
|
410
|
+
previous = self.data.get("matrix_plan_sha256")
|
|
411
|
+
if previous is not None and previous != plan["plan_sha256"]:
|
|
412
|
+
raise RunStateError("冻结 matrix plan 身份已改变")
|
|
413
|
+
names = [MATRIX_PLAN_NAME]
|
|
414
|
+
if plan["identity"]["prune_startup_oom"]:
|
|
415
|
+
names.append(PROBE_NAME)
|
|
416
|
+
for name in names:
|
|
417
|
+
name = str(self.layout.path(name).relative_to(self.directory))
|
|
418
|
+
checksum = file_sha256(self.artifact_path(name))
|
|
419
|
+
recorded = self.data["artifacts"].get(name)
|
|
420
|
+
if recorded is not None and recorded != checksum:
|
|
421
|
+
raise RunStateError(f"冻结计划或 probe 证据已改变:{name}")
|
|
422
|
+
self.data["artifacts"][name] = checksum
|
|
423
|
+
self.data["matrix_plan_sha256"] = plan["plan_sha256"]
|
|
424
|
+
self.save()
|
|
425
|
+
|
|
426
|
+
def bind_runtime(self, task, image, planned, compute_plan: str, execution_plan: str) -> None:
|
|
427
|
+
paths = [str(self.layout.path(name).relative_to(self.directory))
|
|
428
|
+
for name in ("static_meta.json", "collection_history.json", "input_scale_plan.json")]
|
|
429
|
+
for value in (compute_plan, execution_plan):
|
|
430
|
+
if value:
|
|
431
|
+
paths.append(str(Path(value).resolve().relative_to(self.directory)))
|
|
432
|
+
for name in paths:
|
|
433
|
+
path = self.artifact_path(name)
|
|
434
|
+
if path.is_file():
|
|
435
|
+
self.data["artifacts"][name] = file_sha256(path)
|
|
436
|
+
plan = asdict(planned)
|
|
437
|
+
if plan["plan_file"]:
|
|
438
|
+
plan["plan_file"] = str(Path(plan["plan_file"]).resolve().relative_to(self.directory))
|
|
439
|
+
self.data["runtime"] = {
|
|
440
|
+
"task": {key: value for key, value in asdict(task).items() if key != "repository_sources"},
|
|
441
|
+
"image": asdict(image), "planned": plan,
|
|
442
|
+
"compute_plan": str(Path(compute_plan).resolve().relative_to(self.directory)) if compute_plan else "",
|
|
443
|
+
"execution_plan": str(Path(execution_plan).resolve().relative_to(self.directory)) if execution_plan else "",
|
|
444
|
+
}
|
|
445
|
+
self.data["status"] = "running"
|
|
446
|
+
self.save()
|
|
447
|
+
|
|
448
|
+
def restore_runtime(self):
|
|
449
|
+
from acprof.host.detect import TaskInfo
|
|
450
|
+
from acprof.host.images.runtime import ImageInfo, require_image_identity
|
|
451
|
+
from acprof.host.input_plan import PlannedInputScales
|
|
452
|
+
snapshot = self.data["runtime"]
|
|
453
|
+
image = ImageInfo(**snapshot["image"])
|
|
454
|
+
if not image.tag.startswith("sha256:"):
|
|
455
|
+
raise RunStateError("恢复要求原实验的不可变 image ID;历史标签镜像请使用新输出目录")
|
|
456
|
+
require_image_identity(image.tag, image.runtime_environment)
|
|
457
|
+
plan = dict(snapshot["planned"])
|
|
458
|
+
if plan.get("plan_file"):
|
|
459
|
+
plan["plan_file"] = str(self.artifact_path(plan["plan_file"]))
|
|
460
|
+
return (TaskInfo(**snapshot["task"]), image, PlannedInputScales(**plan),
|
|
461
|
+
str(self.artifact_path(snapshot["compute_plan"])) if snapshot["compute_plan"] else "",
|
|
462
|
+
str(self.artifact_path(snapshot["execution_plan"])) if snapshot["execution_plan"] else "")
|
|
463
|
+
|
|
464
|
+
def expected(self, cpu=None, mem=None, gpu=None):
|
|
465
|
+
return _expected(self.data, cpu, mem, gpu)
|
|
466
|
+
|
|
467
|
+
def prepare_case(self, filename: str, cpu: int, mem: int, gpu: str) -> str | None:
|
|
468
|
+
path = (self.layout.case("", cpu, mem, gpu).csv if self.layout.layout_version == 2
|
|
469
|
+
else self.artifact_path(filename))
|
|
470
|
+
filename = str(path.relative_to(self.directory))
|
|
471
|
+
case = self.layout.case_from_csv(path)
|
|
472
|
+
record = self.data["cases"].get(filename, {})
|
|
473
|
+
if record.get("status") == "complete":
|
|
474
|
+
if not path.is_file() or file_sha256(path) != record["sha256"]:
|
|
475
|
+
raise RunStateError(f"已完成 case 的 CSV 缺失或改变:{path}")
|
|
476
|
+
read_result_csv(path, expected=self.expected(cpu, mem, gpu))
|
|
477
|
+
return str(path)
|
|
478
|
+
if record.get("case_attempt_id") and record.get("measurement_status") != "complete":
|
|
479
|
+
from acprof.host.measurement.checkpoint import discover_measurement_checkpoint
|
|
480
|
+
try:
|
|
481
|
+
reference = discover_measurement_checkpoint(
|
|
482
|
+
case, run_id=self.data["run_id"], attempt_id=record["case_attempt_id"],
|
|
483
|
+
expected=self.expected(cpu, mem, gpu),
|
|
484
|
+
)
|
|
485
|
+
except (OSError, ValueError, KeyError) as exc:
|
|
486
|
+
raise RunStateError(f"已发布测量快照无法恢复:{exc}") from exc
|
|
487
|
+
if reference is not None:
|
|
488
|
+
record.update(status="measured", measurement_status="complete", processing_status="pending",
|
|
489
|
+
measurement_checkpoint=reference, recovered_checkpoint_at=utc_now())
|
|
490
|
+
self.save()
|
|
491
|
+
if record.get("measurement_status") == "complete":
|
|
492
|
+
self.processing_checkpoint(str(path), cpu, mem, gpu)
|
|
493
|
+
return None
|
|
494
|
+
candidates = [*case.temporary_files(), case.retained_requests, case.idle]
|
|
495
|
+
existing = list(dict.fromkeys(candidate for candidate in candidates if candidate.exists() or candidate.is_symlink()))
|
|
496
|
+
previous_attempts = list(record.get("previous_attempts", []))
|
|
497
|
+
previous = {name: value for name, value in record.items() if name != "previous_attempts"}
|
|
498
|
+
if existing:
|
|
499
|
+
backup = self.layout.path("interrupted_cases") / case.case_id / uuid4().hex
|
|
500
|
+
backup.mkdir(parents=True)
|
|
501
|
+
backup_directories: set[Path] = set()
|
|
502
|
+
# Preserve every source before removing any file, including partial PCAPs.
|
|
503
|
+
for source in existing:
|
|
504
|
+
self.artifact_path(str(source.relative_to(self.directory)))
|
|
505
|
+
if source.is_symlink() or not source.is_file():
|
|
506
|
+
raise RunStateError(f"不支持的 case 产物类型:{source}")
|
|
507
|
+
# Raw samples and idle diagnostics share case IDs; retain provenance.
|
|
508
|
+
target = backup / source.relative_to(self.directory)
|
|
509
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
510
|
+
shutil.copy2(source, target)
|
|
511
|
+
with target.open("rb") as stream:
|
|
512
|
+
os.fsync(stream.fileno())
|
|
513
|
+
backup_directories.add(target.parent)
|
|
514
|
+
for directory in sorted(backup_directories, key=lambda path: len(path.parts), reverse=True):
|
|
515
|
+
_sync_directory_chain(directory, self.directory)
|
|
516
|
+
self.data["cases"][filename] = {"status": "archived", "backup": str(backup.relative_to(self.directory))}
|
|
517
|
+
previous["backup"] = str(backup.relative_to(self.directory))
|
|
518
|
+
self.save()
|
|
519
|
+
for source in existing:
|
|
520
|
+
source.unlink()
|
|
521
|
+
if previous:
|
|
522
|
+
previous_attempts.append(previous)
|
|
523
|
+
self.data["cases"][filename] = {
|
|
524
|
+
"status": "running", "started_at": utc_now(), "case_attempt_id": uuid4().hex,
|
|
525
|
+
"measurement_status": "running", "processing_status": "pending",
|
|
526
|
+
"resources": [cpu, mem, gpu],
|
|
527
|
+
"previous_attempts": previous_attempts,
|
|
528
|
+
}
|
|
529
|
+
self.save()
|
|
530
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
def case_attempt_id(self, path: str) -> str:
|
|
534
|
+
record = self.data["cases"][str(Path(path).relative_to(self.directory))]
|
|
535
|
+
return record["case_attempt_id"]
|
|
536
|
+
|
|
537
|
+
def bind_case_container(self, path: str, container_id: str) -> None:
|
|
538
|
+
"""Record cleanup ownership before acquisition, including crash recovery."""
|
|
539
|
+
if not re.fullmatch(r"[a-f0-9]{64}", container_id):
|
|
540
|
+
raise RunStateError("Case container must have an immutable Docker ID")
|
|
541
|
+
record = self.data["cases"][str(Path(path).relative_to(self.directory))]
|
|
542
|
+
record.update(cleanup_container_id=container_id, cleanup_status="pending")
|
|
543
|
+
self.save()
|
|
544
|
+
|
|
545
|
+
def checkpoint_measurement(self, path: str, cpu: int, mem: int, gpu: str, *, processing: dict,
|
|
546
|
+
container_id: str) -> None:
|
|
547
|
+
from acprof.host.measurement.checkpoint import publish_measurement_checkpoint
|
|
548
|
+
key = str(Path(path).relative_to(self.directory))
|
|
549
|
+
record = self.data["cases"][key]
|
|
550
|
+
reference = publish_measurement_checkpoint(
|
|
551
|
+
self.layout.case_from_csv(path), run_id=self.data["run_id"],
|
|
552
|
+
attempt_id=record["case_attempt_id"], processing=processing,
|
|
553
|
+
expected=self.expected(cpu, mem, gpu),
|
|
554
|
+
)
|
|
555
|
+
record.update(status="measured", measurement_status="complete", measured_at=utc_now(),
|
|
556
|
+
processing_status="pending", measurement_checkpoint=reference, cleanup_status="pending",
|
|
557
|
+
cleanup_container_id=container_id)
|
|
558
|
+
self.save()
|
|
559
|
+
|
|
560
|
+
def processing_checkpoint(self, path: str, cpu: int, mem: int, gpu: str) -> dict | None:
|
|
561
|
+
from acprof.host.measurement.checkpoint import read_measurement_checkpoint
|
|
562
|
+
record = self.data["cases"].get(str(Path(path).relative_to(self.directory)), {})
|
|
563
|
+
if record.get("measurement_status") != "complete":
|
|
564
|
+
return None
|
|
565
|
+
try:
|
|
566
|
+
checkpoint = read_measurement_checkpoint(
|
|
567
|
+
self.layout.case_from_csv(path), record["measurement_checkpoint"],
|
|
568
|
+
run_id=self.data["run_id"], attempt_id=record["case_attempt_id"],
|
|
569
|
+
expected=self.expected(cpu, mem, gpu),
|
|
570
|
+
)
|
|
571
|
+
except (OSError, ValueError, KeyError) as exc:
|
|
572
|
+
raise RunStateError(f"已完成测量的原始证据无效:{exc}") from exc
|
|
573
|
+
if record.get("cleanup_status") != "complete":
|
|
574
|
+
from acprof.host import command as host_command
|
|
575
|
+
from acprof.host.container_lifecycle import recover_cleanup_debt
|
|
576
|
+
# The existing lifecycle protocol removes only verified abandoned
|
|
577
|
+
# containers, then proves the recorded immutable ID is absent.
|
|
578
|
+
recovery = recover_cleanup_debt(
|
|
579
|
+
[{"container_id": record.get("cleanup_container_id", "")}], host_command.run_command,
|
|
580
|
+
)
|
|
581
|
+
case = self.layout.case_from_csv(path)
|
|
582
|
+
error = case.sidecar("cleanup_error")
|
|
583
|
+
if error.exists():
|
|
584
|
+
target = self.layout.contained(f"raw/cleanup/{case.case_id}/{record['case_attempt_id']}.json")
|
|
585
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
586
|
+
if target.exists():
|
|
587
|
+
raise RunStateError(f"Cleanup evidence already exists: {target}")
|
|
588
|
+
replace_file_durably(error, target)
|
|
589
|
+
record.update(cleanup_status="complete", cleanup_recovery=recovery)
|
|
590
|
+
self.save()
|
|
591
|
+
return checkpoint
|
|
592
|
+
|
|
593
|
+
def record_case_processing(self, path: str, status: str) -> None:
|
|
594
|
+
if status not in {"running", "failed", "complete"}:
|
|
595
|
+
raise ValueError(f"Invalid case processing status: {status}")
|
|
596
|
+
record = self.data["cases"][str(Path(path).relative_to(self.directory))]
|
|
597
|
+
record["processing_status"] = status
|
|
598
|
+
if status == "running":
|
|
599
|
+
record["processing_attempts"] = record.get("processing_attempts", 0) + 1
|
|
600
|
+
self.save()
|
|
601
|
+
|
|
602
|
+
def record_case_cleanup(self, path: str, status: str) -> None:
|
|
603
|
+
if status not in {"complete", "failed"}:
|
|
604
|
+
raise ValueError(f"Invalid case cleanup status: {status}")
|
|
605
|
+
record = self.data["cases"][str(Path(path).relative_to(self.directory))]
|
|
606
|
+
if record.get("measurement_status") == "complete":
|
|
607
|
+
record["cleanup_status"] = status
|
|
608
|
+
self.save()
|
|
609
|
+
|
|
610
|
+
def finish_case(self, path: str, cpu: int, mem: int, gpu: str) -> None:
|
|
611
|
+
_, rows = read_result_csv(path, expected=self.expected(cpu, mem, gpu))
|
|
612
|
+
case = self.layout.case_from_csv(path)
|
|
613
|
+
case.retain_requests()
|
|
614
|
+
key = str(Path(path).relative_to(self.directory))
|
|
615
|
+
record = self.data["cases"].get(key, {})
|
|
616
|
+
self.data["cases"][key] = {
|
|
617
|
+
**record, "processing_status": "complete",
|
|
618
|
+
"measurement_status": "complete" if record.get("measurement_status") == "complete" else "not_measured",
|
|
619
|
+
"status": "complete", "completed_at": utc_now(), "sha256": file_sha256(path),
|
|
620
|
+
"row_count": len(rows), "error_rows": sum(row.get("status") == "error" for row in rows),
|
|
621
|
+
}
|
|
622
|
+
self.save()
|
|
623
|
+
|
|
624
|
+
def finish(self, final_csv: str) -> None:
|
|
625
|
+
_, rows = read_result_csv(final_csv, expected=self.expected())
|
|
626
|
+
self.data.update(status="complete", completed_at=utc_now(), result_sha256=file_sha256(final_csv),
|
|
627
|
+
row_count=len(rows), outcome="partial" if any(row.get("status") == "error" for row in rows) else "ok")
|
|
628
|
+
self.save()
|
|
629
|
+
|
|
630
|
+
def close(self, outcome: str = "interrupted") -> None:
|
|
631
|
+
try:
|
|
632
|
+
if not self.complete:
|
|
633
|
+
self.data["status"] = outcome
|
|
634
|
+
self.data["attempts"][-1]["ended_at"] = utc_now()
|
|
635
|
+
self.data["attempts"][-1]["outcome"] = self.data["status"]
|
|
636
|
+
self.save()
|
|
637
|
+
finally:
|
|
638
|
+
self.lock.__exit__(None, None, None)
|
|
639
|
+
self.measurement_lock.__exit__(None, None, None)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Explicit host overrides that select a locked inference environment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def runtime_build_overrides() -> dict[str, str]:
|
|
8
|
+
"""Keep preparation, fingerprints and coverage recovery on the same inputs."""
|
|
9
|
+
return {name: os.environ.get(name, "").strip() for name in (
|
|
10
|
+
"ACPROF_NLP_TORCH_INDEX_URL", "ACPROF_NLP_TORCH_SPEC", "ACPROF_HOST_CUDA_VERSION")}
|