Skip to content

build(deps): bump nvidia/tensorrt from 24.05-py3 to 26.03-py3 in /deploy/runpod #224

build(deps): bump nvidia/tensorrt from 24.05-py3 to 26.03-py3 in /deploy/runpod

build(deps): bump nvidia/tensorrt from 24.05-py3 to 26.03-py3 in /deploy/runpod #224

Workflow file for this run

name: ci
on:
push:
branches-ignore:
- beads-sync
- "ci-logs/**"
pull_request:
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# Dependency tier policy:
# - core: hash-locked runtime deps + local wheel install (packaging + CLI smoke)
# - docs_mcp: hash-locked docs/MCP/Actions deps from exact top-level lock
# - recommended: hash-locked requirements-ci.lock (regression + schema/manifest gates)
# - full: GPU/backends in separate manual/nightly workflows
# docs: docs/ci_dependency_tiers.md
changes:
name: change scope
runs-on: ubuntu-latest
outputs:
full_ci: ${{ steps.filter.outputs.full_ci }}
docs_mcp_ci: ${{ steps.filter.outputs.docs_mcp_ci }}
manual_ci: ${{ steps.filter.outputs.manual_ci }}
workflows_ci: ${{ steps.filter.outputs.workflows_ci }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Detect change scope
id: filter
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
with:
filters: |
full_ci:
- 'yolozu/**'
- '!yolozu/integrations/mcp_server.py'
- '!yolozu/integrations/actions_api.py'
- 'tools/**'
- '!tools/run_mcp_server.py'
- '!tools/run_actions_api.py'
- '!tools/check_mcp_settings.py'
- 'scripts/**'
- 'rtdetr_pose/**'
- 'tests/**'
- '!tests/test_ai_first_mcp_surface.py'
- '!tests/test_integrations_mcp_actions_parity.py'
- '!tests/test_check_mcp_settings_tool.py'
- '!tests/test_generated_integration_tool_reference.py'
- 'configs/**'
- 'data/**'
- 'baselines/**'
- 'requirements*.txt'
- 'requirements*.lock'
- 'pyproject.toml'
- 'pytest.ini'
- 'ruff.toml'
- 'MANIFEST.in'
- 'setup.py'
- 'setup.cfg'
docs_mcp_ci:
- 'README.md'
- 'Readme_jp.md'
- 'docs/**'
- 'tools/run_mcp_server.py'
- 'tools/run_actions_api.py'
- 'tools/check_mcp_settings.py'
- 'yolozu/integrations/mcp_server.py'
- 'yolozu/integrations/actions_api.py'
- 'tests/test_ai_first_mcp_surface.py'
- 'tests/test_integrations_mcp_actions_parity.py'
- 'tests/test_check_mcp_settings_tool.py'
- 'tests/test_generated_integration_tool_reference.py'
manual_ci:
- 'manual/**'
workflows_ci:
- '.github/workflows/**'
docs_meta:
name: docs/metadata fast path
needs:
- changes
if: ${{ needs.changes.outputs.full_ci != 'true' && needs.changes.outputs.docs_mcp_ci != 'true' && needs.changes.outputs.manual_ci != 'true' && needs.changes.outputs.workflows_ci != 'true' }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for docs-only/metadata-only changes
run: echo "No code/runtime changes detected. Heavy CI jobs are skipped."
workflows_meta:
name: workflows fast path
needs:
- changes
if: ${{ needs.changes.outputs.full_ci != 'true' && needs.changes.outputs.docs_mcp_ci != 'true' && needs.changes.outputs.manual_ci != 'true' && needs.changes.outputs.workflows_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Skip heavy CI for workflow-only changes
run: echo "Only .github/workflows/** changed. Heavy CI jobs are skipped."
manual_build:
name: manual build (main.tex only)
needs:
- changes
if: ${{ needs.changes.outputs.full_ci != 'true' && needs.changes.outputs.docs_mcp_ci != 'true' && needs.changes.outputs.manual_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Build manual PDF (main.tex)
uses: xu-cheng/latex-action@25080975331dc4fc8e6f1af55788e00f84f51fff # v4
with:
working_directory: manual
root_file: main.tex
args: -pdf -interaction=nonstopmode -halt-on-error -outdir=build -jobname=yolozu_manual
pre_compile: python3 ../tools/generate_manual_import_graphs.py
- name: Verify manual artifact
run: |
test -f manual/build/yolozu_manual.pdf
ls -lh manual/build/yolozu_manual.pdf
docs_mcp_gate:
name: "ci tier: docs+mcp (lightweight)"
needs:
- changes
if: ${{ needs.changes.outputs.full_ci != 'true' && needs.changes.outputs.docs_mcp_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install lightweight deps (hash-locked docs/MCP/actions set)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-docs-actions.lock
- name: Manifest + docs references gate
run: |
python tools/validate_tool_manifest.py --manifest tools/manifest.json --require-declarative
python -m unittest tests.test_packaged_tools_manifest tests.test_manifest_docs_references -v
- name: MCP/actions smoke
run: |
python3 tools/run_mcp_server.py --help
python3 tools/run_mcp_server.py --sample-generate-config > reports/ai_generate_config_ci.json
python3 tools/run_mcp_server.py --sample-review-config reports/ai_generate_config_ci.json
python3 tools/run_actions_api.py --help
python3 tools/check_mcp_settings.py --output reports/mcp_settings_check_ci.json
- name: MCP-focused tests
run: |
python -m unittest \
tests.test_ai_first_mcp_surface \
tests.test_integrations_mcp_actions_parity \
tests.test_check_mcp_settings_tool \
tests.test_generated_integration_tool_reference -v
smoke_gate:
name: "ci tier: core (runtime smoke)"
needs:
- changes
if: ${{ needs.changes.outputs.full_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install (hash-locked runtime + local wheel)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-runtime.lock --install-local-wheel
- name: README smoke script
run: |
bash scripts/smoke.sh
quality_gate:
name: "ci tier: recommended (ruff + focused pytest)"
needs:
- changes
- smoke_gate
if: ${{ needs.changes.outputs.full_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install deps (hash-locked recommended tier)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-ci.lock
- name: Ruff (focused paths)
run: |
ruff check tools yolozu tests
- name: Focused pytest (YOLO26 quality)
run: |
pytest -q \
tests/test_eval_suite_export_settings.py \
tests/test_eval_suite_determinism.py \
tests/test_check_map_targets_contract.py \
tests/test_yolo26_protocol.py \
tests/test_predictions.py \
tests/test_manifest_docs_references.py \
tests/test_backend_shape_format_contracts.py \
tests/test_external_inference_templates_smoke.py \
tests/test_ai_first_mcp_surface.py
- name: Smoke walkthrough (deep, offline)
run: |
bash scripts/smoke.sh --profile deep --torch-device cpu
- name: MCP/AI surface smoke
run: |
python3 tools/run_mcp_server.py --help
python3 tools/run_mcp_server.py --sample-generate-config > reports/ai_generate_config_ci.json
python3 tools/run_mcp_server.py --sample-review-config reports/ai_generate_config_ci.json
python3 tools/run_actions_api.py --help
pip_smoke:
name: "ci tier: core (pip smoke, py${{ matrix.python-version }})"
needs:
- changes
- smoke_gate
if: ${{ needs.changes.outputs.full_ci == 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ matrix.python-version }}
- name: Install (hash-locked runtime + local wheel)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-runtime.lock --install-local-wheel
- name: Repo smoke script (offline dry-run path)
run: |
bash scripts/smoke.sh
- name: CLI smoke (doctor + demo)
run: |
yolozu doctor --output -
yolozu demo instance-seg --num-images 2 --image-size 48 --max-instances 2 --run-dir "${RUNNER_TEMP}/yolozu_demo"
test:
name: "ci tier: recommended (full gate suite)"
needs: [changes, smoke_gate, quality_gate]
if: ${{ needs.changes.outputs.full_ci == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install deps (hash-locked recommended tier)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-ci.lock
- name: Packaging smoke (hash-locked local wheel + yolozu doctor)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-runtime.lock --install-local-wheel
yolozu doctor --output -
yolozu export --help
python -c "from yolozu import resources; paths = resources.list_resource_paths(); assert 'schemas/predictions.schema.json' in paths; assert 'protocols/yolo26_eval.json' in paths; print('resources OK:', len(paths))"
- name: Install build tooling (hash-locked)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-build.lock
- name: Wheel contents gate (schemas/protocols/minimal docs)
run: |
python -m build --wheel
python - <<'PY'
import glob
import sys
import zipfile
wheels = sorted(glob.glob("dist/*.whl"))
if not wheels:
raise SystemExit("no wheels found in dist/")
whl = wheels[-1]
with zipfile.ZipFile(whl, "r") as z:
names = set(z.namelist())
required = [
"yolozu/data/schemas/predictions.schema.json",
"yolozu/data/protocols/yolo26_eval.json",
"yolozu/data/manifest/tools_manifest.json",
"yolozu/data/docs/README.md",
"yolozu/data/docs/proof_onepager.md",
"yolozu/data/docs/backend_parity_matrix.md",
"yolozu/data/docs/onnx_export_parity.md",
"yolozu/data/docs/tensorrt_pipeline.md",
"yolozu/data/docs/yolo26_baseline_repro.md",
"yolozu/data/docs/tta_support_matrix.md",
"yolozu/data/docs/adapter_strategy.md",
]
missing = [p for p in required if p not in names]
if missing:
print("wheel:", whl)
print("missing:")
for p in missing:
print(" -", p)
sys.exit(1)
print("wheel contents OK:", whl)
PY
- name: Sdist contents gate (schemas/protocols/docs/manifest)
run: |
python -m build --sdist
python - <<'PY'
import glob
import tarfile
import sys
sdist_files = sorted(glob.glob("dist/*.tar.gz"))
if not sdist_files:
raise SystemExit("no sdist found in dist/")
sdist = sdist_files[-1]
required = [
"LICENSE",
"NOTICE",
"COPYRIGHT",
"SECURITY.md",
"RELEASE.md",
"tools/manifest.json",
"yolozu/data/schemas/predictions.schema.json",
"yolozu/data/protocols/yolo26_eval.json",
"yolozu/data/manifest/tools_manifest.json",
"yolozu/data/docs/README.md",
"yolozu/data/docs/proof_onepager.md",
"yolozu/data/docs/backend_parity_matrix.md",
"yolozu/data/docs/onnx_export_parity.md",
"yolozu/data/docs/tensorrt_pipeline.md",
"yolozu/data/docs/yolo26_baseline_repro.md",
"yolozu/data/docs/tta_support_matrix.md",
"yolozu/data/docs/adapter_strategy.md",
]
with tarfile.open(sdist, "r:gz") as tf:
names = tf.getnames()
def has_suffix(path_suffix: str) -> bool:
suffix = "/" + path_suffix
return any(name.endswith(suffix) for name in names)
missing = [path for path in required if not has_suffix(path)]
if missing:
print("sdist:", sdist)
print("missing:")
for path in missing:
print(" -", path)
sys.exit(1)
print("sdist contents OK:", sdist)
PY
- name: License policy
run: |
python tools/check_license_policy.py
- name: Lint
run: |
ruff check .
- name: Manifest declarative completeness gate
run: |
python tools/validate_tool_manifest.py --manifest tools/manifest.json --require-declarative
- name: Manifest sync gate (tools -> packaged copy)
run: |
python tools/sync_tools_manifest.py
git diff --exit-code -- tools/manifest.json yolozu/data/manifest/tools_manifest.json
- name: Advisory audit (yolozu flags vs manifest)
continue-on-error: true
run: |
python tools/audit_yolozu_manifest_flags.py
- name: Reference adapter micro gate - interface contract (hard)
run: |
python tools/run_reference_adapter_regression.py \
--dataset data/smoke \
--split val \
--max-images 2 \
--profile micro \
--device cpu \
--image-size 160 \
--score-threshold 0.05 \
--max-detections 20 \
--init-seed 2026 \
--repro-policy relaxed \
--capture-provenance minimal \
--score-gate-mode off \
--perf-gate-mode off \
--runtime-lock requirements-ci.lock \
--enforce-runtime-lock \
--enforce-weights-hash \
--baseline baselines/reference_adapter/rtdetr_pose_smoke_val.json \
--output reports/reference_adapter_regression_contract_ci.json
- name: Reference adapter micro gate - behavior (warn)
run: |
python tools/run_reference_adapter_regression.py \
--dataset data/smoke \
--split val \
--max-images 2 \
--profile micro \
--device cpu \
--image-size 160 \
--score-threshold 0.05 \
--max-detections 20 \
--init-seed 2026 \
--repro-policy relaxed \
--capture-provenance minimal \
--schema-gate-mode off \
--consistency-gate-mode off \
--score-gate-mode warn \
--perf-gate-mode warn \
--runtime-lock requirements-ci.lock \
--enforce-runtime-lock \
--baseline baselines/reference_adapter/rtdetr_pose_smoke_val.json \
--output reports/reference_adapter_regression_behavior_ci.json
- name: Reference adapter fixed real scenario smoke (write/check)
run: |
# CI does not ship real images in git; prepare a tiny real-image few-shot dataset deterministically.
python tools/prepare_real_multitask_fewshot.py \
--out data/real_multitask_fewshot \
--train-images 1 \
--val-images 1 \
--download-if-missing \
--allow-auto-download \
--accept-dataset-license \
--download-num-images 2 \
--download-seed 0 \
--strict-provenance \
--force
baseline_path="reports/reference_adapter_real_multitask_micro_baseline_ci.json"
python tools/run_reference_adapter_regression.py \
--dataset data/real_multitask_fewshot \
--split val \
--max-images 1 \
--profile micro \
--device cpu \
--image-size 160 \
--score-threshold 0.05 \
--max-detections 20 \
--init-seed 2026 \
--repro-policy relaxed \
--capture-provenance minimal \
--score-gate-mode off \
--perf-gate-mode off \
--runtime-lock requirements-ci.lock \
--enforce-runtime-lock \
--baseline "${baseline_path}" \
--write-baseline \
--output reports/reference_adapter_regression_real_scenario_baseline_write_ci.json
python tools/run_reference_adapter_regression.py \
--dataset data/real_multitask_fewshot \
--split val \
--max-images 1 \
--profile micro \
--device cpu \
--image-size 160 \
--score-threshold 0.05 \
--max-detections 20 \
--init-seed 2026 \
--repro-policy relaxed \
--capture-provenance minimal \
--score-gate-mode off \
--perf-gate-mode off \
--runtime-lock requirements-ci.lock \
--enforce-runtime-lock \
--baseline "${baseline_path}" \
--output reports/reference_adapter_regression_real_scenario_ci.json
- name: Unit tests
run: |
python -m unittest discover -s tests -t tests
python -m unittest discover -s rtdetr_pose/tests -t rtdetr_pose/tests
- name: Schema compatibility gate
run: |
python3 tools/check_schema_compatibility.py
- name: Golden compatibility gate
run: |
python3 tools/check_golden_compatibility.py
- name: Migration smoke (migrate + validate)
run: |
python - <<'PY'
import os
import json
from pathlib import Path
from PIL import Image
out_dir = Path("reports") / "ci_migrate"
out_dir.mkdir(parents=True, exist_ok=True)
dataset_root = Path(os.environ["RUNNER_TEMP"]) / "ultra_seg_dataset"
images = dataset_root / "images" / "val"
labels = dataset_root / "labels" / "val"
images.mkdir(parents=True, exist_ok=True)
labels.mkdir(parents=True, exist_ok=True)
Image.new("RGB", (32, 32), color=(0, 0, 0)).save(images / "000001.jpg")
(labels / "000001.txt").write_text("0 0.1 0.1 0.9 0.1 0.9 0.9 0.1 0.9\n", encoding="utf-8")
data_yaml = Path(os.environ["RUNNER_TEMP"]) / "ultra_data.yaml"
data_yaml.write_text(
"\n".join(
[
f"path: {dataset_root}",
"train: images/train",
"val: images/val",
"task: segment",
"",
]
),
encoding="utf-8",
)
args_yaml = Path(os.environ["RUNNER_TEMP"]) / "ultra_args.yaml"
args_yaml.write_text(f"data: {data_yaml}\ntask: segment\n", encoding="utf-8")
voc_root = Path(os.environ["RUNNER_TEMP"]) / "voc_smoke" / "VOC2012"
(voc_root / "JPEGImages").mkdir(parents=True, exist_ok=True)
(voc_root / "SegmentationClass").mkdir(parents=True, exist_ok=True)
(voc_root / "ImageSets" / "Segmentation").mkdir(parents=True, exist_ok=True)
(voc_root / "JPEGImages" / "0001.jpg").write_bytes(b"")
(voc_root / "SegmentationClass" / "0001.png").write_bytes(b"")
(voc_root / "ImageSets" / "Segmentation" / "val.txt").write_text("0001\n", encoding="utf-8")
coco_root = Path(os.environ["RUNNER_TEMP"]) / "coco_smoke"
(coco_root / "images" / "val2017").mkdir(parents=True, exist_ok=True)
(coco_root / "annotations").mkdir(parents=True, exist_ok=True)
Image.new("RGB", (64, 32), color=(0, 0, 0)).save(coco_root / "images" / "val2017" / "0001.jpg")
instances = {
"images": [{"id": 1, "file_name": "0001.jpg", "width": 64, "height": 32}],
"annotations": [{"id": 1, "image_id": 1, "category_id": 7, "bbox": [0, 0, 10, 20], "iscrowd": 0}],
"categories": [{"id": 7, "name": "thing"}],
}
(coco_root / "annotations" / "instances_val2017.json").write_text(json.dumps(instances), encoding="utf-8")
results = [{"image_id": 1, "category_id": 7, "bbox": [0, 0, 10, 20], "score": 0.9}]
(coco_root / "results.json").write_text(json.dumps(results), encoding="utf-8")
print("prepared:", dataset_root, data_yaml, args_yaml, voc_root)
PY
yolozu migrate dataset \
--from ultralytics \
--data "${RUNNER_TEMP}/ultra_data.yaml" \
--args "${RUNNER_TEMP}/ultra_args.yaml" \
--output reports/ci_migrate/ultra_wrapper \
--force
yolozu validate dataset reports/ci_migrate/ultra_wrapper --split val
yolozu validate dataset "${RUNNER_TEMP}/ultra_seg_dataset" --split val --label-format segment
yolozu migrate seg-dataset \
--from voc \
--root "${RUNNER_TEMP}/voc_smoke/VOC2012" \
--split val \
--output reports/ci_migrate/voc_seg_dataset.json \
--force
python - <<'PY'
import json
from pathlib import Path
from yolozu.segmentation_dataset import load_seg_dataset_descriptor
d = load_seg_dataset_descriptor("reports/ci_migrate/voc_seg_dataset.json")
assert d.task == "semantic_segmentation"
assert len(d.samples) == 1
preds = Path("reports/ci_migrate/seg_preds.json")
preds.write_text(json.dumps([{"id": "0001", "mask": "pred.png"}]), encoding="utf-8")
print("ok", preds)
PY
yolozu validate seg reports/ci_migrate/seg_preds.json
yolozu migrate dataset \
--from coco \
--coco-root "${RUNNER_TEMP}/coco_smoke" \
--split val2017 \
--output reports/ci_migrate/coco_wrapper \
--force
yolozu validate dataset reports/ci_migrate/coco_wrapper --split val2017
yolozu migrate predictions \
--from coco-results \
--results "${RUNNER_TEMP}/coco_smoke/results.json" \
--instances "${RUNNER_TEMP}/coco_smoke/annotations/instances_val2017.json" \
--output reports/ci_migrate/coco_predictions.json \
--force
yolozu validate predictions reports/ci_migrate/coco_predictions.json --strict
yolozu import dataset \
--from coco-instances \
--instances "${RUNNER_TEMP}/coco_smoke/annotations/instances_val2017.json" \
--images-dir "${RUNNER_TEMP}/coco_smoke/images/val2017" \
--split val2017 \
--output reports/ci_migrate/coco_import_wrapper \
--force
yolozu validate dataset reports/ci_migrate/coco_import_wrapper --split val2017
- name: Upload migration artifacts
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ci_migrate_artifacts
path: reports/ci_migrate
- name: Scenario smoke run
run: |
if bash tools/fetch_coco128.sh; then
python tools/run_scenarios.py --adapter dummy --max-images 10
else
echo "warning: coco128 fetch failed; running dataset-free scenario suite smoke"
python tools/run_scenario_suite.py --output reports/scenario_suite_ci.json
fi
- name: Benchmark publication smoke
run: |
python - <<'PY'
import json
from pathlib import Path
reports = Path("reports")
reports.mkdir(parents=True, exist_ok=True)
a = {
"schema_version": 1,
"timestamp": "2026-01-01T00:00:00Z",
"metrics": {
"buckets": [
{"name": "yolo26n", "metrics": {"fps": 40.0, "latency_ms": {"mean": 25.0}}},
{"name": "yolo26s", "metrics": {"fps": 30.0, "latency_ms": {"mean": 33.0}}},
]
},
"meta": {"run_id": "ci_bench_a", "git_head": "deadbeef"},
}
b = {
"schema_version": 1,
"timestamp": "2026-01-01T00:01:00Z",
"metrics": {"summary": {"fps_mean": 20.0, "latency_ms_mean": 50.0}},
"meta": {"run_id": "ci_bench_b", "git_head": "deadbeef"},
}
(reports / "benchmark_latency_ci_a.json").write_text(json.dumps(a), encoding="utf-8")
(reports / "benchmark_latency_ci_b.json").write_text(json.dumps(b), encoding="utf-8")
PY
python tools/publish_benchmark_table.py \
--report reports/benchmark_latency_ci_a.json \
--report reports/benchmark_latency_ci_b.json \
--output-json reports/benchmark_table_ci.json \
--output-md reports/benchmark_table_ci.md \
--source-command "python3 tools/benchmark_latency.py --config configs/benchmark_latency_example.json"
test -f reports/benchmark_table_ci.json
test -f reports/benchmark_table_ci.md
python - <<'PY'
import json
from pathlib import Path
payload = json.loads(Path("reports/benchmark_table_ci.json").read_text(encoding="utf-8"))
rows = payload.get("rows") or []
run_ids = {str(r.get("run_id")) for r in rows}
assert "ci_bench_a" in run_ids and "ci_bench_b" in run_ids
assert payload.get("protocol", {}).get("id") == "yolo26"
print("benchmark publication smoke passed")
PY
- name: Training contract smoke (dry-run + ONNX export/parity)
run: |
python - <<'PY'
import os
from pathlib import Path
from PIL import Image
root = Path(os.environ["RUNNER_TEMP"]) / "yolozu_tiny_dataset"
for split in ("train2017", "val2017"):
images = root / "images" / split
labels = root / "labels" / split
images.mkdir(parents=True, exist_ok=True)
labels.mkdir(parents=True, exist_ok=True)
for idx in range(2):
name = f"{idx:06d}"
img_path = images / f"{name}.jpg"
Image.new("RGB", (32, 32), color=(0, 0, 0)).save(img_path)
(labels / f"{name}.txt").write_text("0 0.5 0.5 0.2 0.2\n", encoding="utf-8")
print(root)
PY
yolozu train configs/examples/train_contract.yaml \
--run-id ci_smoke \
--dry-run \
--dataset-root "${RUNNER_TEMP}/yolozu_tiny_dataset" \
--split train2017 \
--val-split val2017 \
--device cpu \
--amp none \
--ddp-backend gloo \
--image-size 64 \
--batch-size 1
test -f runs/ci_smoke/reports/run_meta.json
test -f runs/ci_smoke/reports/config_resolved.yaml
test -f runs/ci_smoke/checkpoints/last.pt
test -f runs/ci_smoke/checkpoints/best.pt
test -f runs/ci_smoke/exports/model.onnx
test -f runs/ci_smoke/reports/onnx_parity.json
python - <<'PY'
import json
from pathlib import Path
from yolozu.run_record import validate_run_record_contract
meta = json.loads(Path("runs/ci_smoke/reports/run_meta.json").read_text(encoding="utf-8"))
validate_run_record_contract(meta, require_git_sha=True)
assert "dependency_lock" in meta and isinstance(meta["dependency_lock"], dict)
assert "preprocess" in meta and isinstance(meta["preprocess"], dict)
assert "command" in meta and isinstance(meta["command"], dict)
print("run_meta keys OK")
PY
- name: DDP smoke (torchrun, CPU gloo, rank0-only artifacts)
run: |
python - <<'PY'
import os
from pathlib import Path
dataset_root = Path(os.environ["RUNNER_TEMP"]) / "yolozu_tiny_dataset"
cfg = Path(os.environ["RUNNER_TEMP"]) / "rtdetr_pose_ci_ddp.yaml"
cfg.write_text(
"\n".join(
[
"config_version: 1",
f"dataset_root: {dataset_root.as_posix()}",
"split: train2017",
"val_split: val2017",
"seed: 0",
"device: cpu",
"amp: none",
"ddp: true",
"batch_size: 1",
"val_batch_size: 1",
"epochs: 1",
"log_every: 1",
"val_every: 0",
"checkpoint_every: 100",
"run_id: ci_smoke_ddp",
"dry_run: true",
"model_config: rtdetr_pose/configs/base.json",
]
)
+ "\n",
encoding="utf-8",
)
print(cfg)
PY
TORCH_DISTRIBUTED_DEFAULT_FIND_UNUSED_PARAMETERS=1 \
torchrun --standalone --nproc_per_node=2 rtdetr_pose/tools/train_minimal.py \
--config "${RUNNER_TEMP}/rtdetr_pose_ci_ddp.yaml" \
--run-contract
test -f runs/ci_smoke_ddp/reports/run_meta.json
test -f runs/ci_smoke_ddp/reports/config_resolved.yaml
test -f runs/ci_smoke_ddp/checkpoints/last.pt
test -f runs/ci_smoke_ddp/checkpoints/best.pt
test -f runs/ci_smoke_ddp/exports/model.onnx
test -f runs/ci_smoke_ddp/reports/onnx_parity.json
- name: Backend parity matrix smoke (JSON + HTML)
run: |
python - <<'PY'
import json
import os
import subprocess
import sys
from pathlib import Path
from PIL import Image
root = Path(os.environ["RUNNER_TEMP"]) / "backend_parity_matrix_smoke"
root.mkdir(parents=True, exist_ok=True)
image = root / "000001.jpg"
Image.new("RGB", (8, 8), color=(0, 0, 0)).save(image)
payload = [
{
"image": str(image.resolve()),
"detections": [
{
"class_id": 0,
"score": 0.9,
"bbox": {"cx": 0.5, "cy": 0.5, "w": 0.2, "h": 0.2},
}
],
}
]
args = []
for backend in ("torch", "onnxrt", "trt", "opencv_dnn", "custom_cpp"):
path = root / f"pred_{backend}.json"
path.write_text(json.dumps(payload), encoding="utf-8")
args.extend(["--backend-predictions", f"{backend}={path}"])
run_dir = Path("runs") / "backend_parity_matrix" / "ci_smoke"
proc = subprocess.run(
[
sys.executable,
"tools/backend_parity_matrix.py",
*args,
"--reference-backend",
"torch",
"--image-size",
"8,8",
"--run-dir",
str(run_dir),
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
check=False,
)
if proc.returncode != 0:
raise SystemExit(f"backend parity matrix smoke failed\nSTDOUT:\n{proc.stdout}\nSTDERR:\n{proc.stderr}")
report_json = run_dir / "reports" / "backend_parity_matrix.json"
report_html = run_dir / "reports" / "backend_parity_matrix.html"
if not report_json.is_file() or not report_html.is_file():
raise SystemExit(f"missing outputs: {report_json} {report_html}")
report = json.loads(report_json.read_text(encoding="utf-8"))
if not report.get("ok"):
raise SystemExit("backend parity matrix smoke produced non-ok report")
if len(report.get("matrix") or []) != 4:
raise SystemExit("backend parity matrix smoke report has unexpected matrix size")
print("backend parity matrix smoke passed")
PY
- name: PyInstaller smoke (build + resources list)
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-pyinstaller.lock
pyinstaller -y -n yolozu_bin \
deploy/pyinstaller/yolozu_entrypoint.py \
--collect-data yolozu.data \
--collect-data rtdetr_pose
./dist/yolozu_bin/yolozu_bin --help
./dist/yolozu_bin/yolozu_bin resources list | head -n 5
- name: PyArmor + PyInstaller smoke (optional)
if: ${{ vars.PYARMOR_SMOKE == '1' }}
run: |
python3 tools/ci/install_with_hashes.py --requirements requirements-pyarmor.lock
rm -rf build/pyarmor_obf dist/yolozu_pyarmor yolozu_pyarmor.spec
pyarmor gen -O build/pyarmor_obf deploy/pyinstaller/yolozu_entrypoint.py
pyinstaller -y -n yolozu_pyarmor \
build/pyarmor_obf/yolozu_entrypoint.py \
--collect-data yolozu.data \
--collect-data rtdetr_pose
./dist/yolozu_pyarmor/yolozu_pyarmor --help