mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-23 02:02:08 +08:00
oo buddy boy
This commit is contained in:
@@ -80,6 +80,7 @@ def parse_args() -> argparse.Namespace:
|
||||
)
|
||||
parser.add_argument("--list", action="store_true", help="List staged models and exit.")
|
||||
parser.add_argument("--force", action="store_true", help="Accepted for compatibility; selected outputs are always replaced.")
|
||||
parser.add_argument("--external-gpu", action="store_true", help="Compile the driving artifact for the USB AMD GPU.")
|
||||
parser.add_argument("--split-artifact", type=Path, help="Split an existing oversized PKL without compiling.")
|
||||
parser.add_argument("--chunk-size-mib", type=int, default=95, help="Multipart size in MiB; must be below 100.")
|
||||
parser.add_argument(
|
||||
@@ -355,6 +356,7 @@ def compile_driving(
|
||||
version: str,
|
||||
output_dir: Path,
|
||||
image_history_pipeline: str,
|
||||
external_gpu: bool = False,
|
||||
) -> Path:
|
||||
model_type, source_args = driving_compile_args(files, input_format)
|
||||
output_path = output_dir / f"{model_key}_driving_tinygrad.pkl"
|
||||
@@ -390,7 +392,20 @@ def compile_driving(
|
||||
]
|
||||
if version:
|
||||
command += ["--behavior-version", version]
|
||||
subprocess.run(command, cwd=REPO_ROOT, env=build_compile_env(), check=True)
|
||||
compile_env = build_compile_env()
|
||||
if external_gpu:
|
||||
for qcom_only_flag in ("IMAGE", "NOLOCALS", "OPENPILOT_HACKS"):
|
||||
compile_env.pop(qcom_only_flag, None)
|
||||
compile_env.update({
|
||||
"DEBUG": "2",
|
||||
"DEV": "USB+AMD:LLVM",
|
||||
"WARP_DEV": "QCOM",
|
||||
"FLOAT16": "1",
|
||||
"JIT_BATCH_SIZE": "0",
|
||||
"GMMU": "0",
|
||||
})
|
||||
command.append("--out-of-band")
|
||||
subprocess.run(command, cwd=REPO_ROOT, env=compile_env, check=True)
|
||||
return output_path
|
||||
|
||||
|
||||
@@ -487,7 +502,8 @@ def main() -> int:
|
||||
version = "v15"
|
||||
version_label = version or "unspecified behavior"
|
||||
print(f"Compiling {model_key} ({input_format}, {version_label}) from {args.input_dir} -> {args.output_dir}")
|
||||
output = compile_driving(model_key, files, input_format, version, args.output_dir, args.image_history_pipeline)
|
||||
output = compile_driving(model_key, files, input_format, version, args.output_dir,
|
||||
args.image_history_pipeline, args.external_gpu)
|
||||
print(f" saved {output.name}")
|
||||
multipart_outputs = split_oversized_artifact(output)
|
||||
if multipart_outputs:
|
||||
|
||||
@@ -236,11 +236,14 @@ def compile_model(model_id: str, source: dict, version: str, workspace: Path, fo
|
||||
run(["rsync", "-az", "--exclude=._*", f"{source_dir}/", f"{REMOTE}:{remote_input}/"])
|
||||
|
||||
log_path = workspace / "logs" / f"{model_id}.log"
|
||||
command = (
|
||||
f"cd {REMOTE_ROOT} && ./models --model {model_id} "
|
||||
f"--input-dir {remote_input} --output-dir {REMOTE_ROOT}/compiledmodels "
|
||||
f"--input-format {source['input_format']} --version {version}"
|
||||
)
|
||||
command_parts = [
|
||||
f"cd {REMOTE_ROOT} && ./models --model {model_id}",
|
||||
f"--input-dir {remote_input} --output-dir {REMOTE_ROOT}/compiledmodels",
|
||||
f"--input-format {source['input_format']} --version {version}",
|
||||
]
|
||||
if source.get("uses_external_gpu"):
|
||||
command_parts.append("--external-gpu")
|
||||
command = " ".join(command_parts)
|
||||
with open(log_path, "wb") as log_file:
|
||||
process = subprocess.run(["ssh", REMOTE, command], stdout=log_file, stderr=subprocess.STDOUT)
|
||||
if process.returncode != 0:
|
||||
@@ -288,7 +291,7 @@ def validate_model(model_id: str, version: str, workspace: Path) -> dict:
|
||||
return payload
|
||||
|
||||
|
||||
def update_manifest(base_manifest: Path, workspace: Path) -> dict:
|
||||
def update_manifest(base_manifest: Path, workspace: Path, source_map: dict) -> dict:
|
||||
payload = load_json(base_manifest)
|
||||
models = payload["models"] if isinstance(payload, dict) else payload
|
||||
if not any(model.get("id") == "deeprl3v2" for model in models):
|
||||
@@ -302,6 +305,9 @@ def update_manifest(base_manifest: Path, workspace: Path) -> dict:
|
||||
})
|
||||
multipart_handoff = []
|
||||
for model in models:
|
||||
source = source_map.get(model["id"], {})
|
||||
if "uses_external_gpu" in source:
|
||||
model["uses_external_gpu"] = bool(source["uses_external_gpu"])
|
||||
artifact = workspace / "compiled" / f"{model['id']}_driving_tinygrad.pkl"
|
||||
model.pop("artifact_format", None)
|
||||
model.pop("artifact_size", None)
|
||||
@@ -350,7 +356,7 @@ def main() -> int:
|
||||
if args.command == "manifest":
|
||||
if args.base_manifest is None:
|
||||
parser.error("--base-manifest is required")
|
||||
update_manifest(args.base_manifest, args.workspace)
|
||||
update_manifest(args.base_manifest, args.workspace, source_map)
|
||||
return 0
|
||||
|
||||
model_ids = [args.model] if args.model else list(source_map)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import statistics
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Benchmark fixed-shape ONNX forwards through OpenCV DNN.")
|
||||
parser.add_argument("models", nargs="+", type=Path, help="ONNX model paths to benchmark.")
|
||||
parser.add_argument("--input-size", type=int, required=True, help="Square model input size.")
|
||||
parser.add_argument("--iterations", type=int, default=50)
|
||||
parser.add_argument("--warmup", type=int, default=5)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def percentile(values: list[float], quantile: float) -> float:
|
||||
ordered = sorted(values)
|
||||
index = min(round((len(ordered) - 1) * quantile), len(ordered) - 1)
|
||||
return ordered[index]
|
||||
|
||||
|
||||
def benchmark(path: Path, input_size: int, iterations: int, warmup: int) -> dict[str, object]:
|
||||
net = cv2.dnn.readNetFromONNX(str(path))
|
||||
blob = np.zeros((1, 3, input_size, input_size), dtype=np.float32)
|
||||
net.setInput(blob)
|
||||
output = net.forward()
|
||||
for _ in range(max(warmup - 1, 0)):
|
||||
net.setInput(blob)
|
||||
output = net.forward()
|
||||
|
||||
durations_ms = []
|
||||
for _ in range(max(iterations, 1)):
|
||||
net.setInput(blob)
|
||||
started_at = time.perf_counter()
|
||||
output = net.forward()
|
||||
durations_ms.append((time.perf_counter() - started_at) * 1000.0)
|
||||
|
||||
return {
|
||||
"model": str(path.resolve()),
|
||||
"input_size": input_size,
|
||||
"output_shape": list(output.shape),
|
||||
"iterations": len(durations_ms),
|
||||
"median_ms": round(statistics.median(durations_ms), 3),
|
||||
"p95_ms": round(percentile(durations_ms, 0.95), 3),
|
||||
"mean_ms": round(statistics.mean(durations_ms), 3),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
results = [
|
||||
benchmark(path.expanduser().resolve(), args.input_size, args.iterations, args.warmup)
|
||||
for path in args.models
|
||||
]
|
||||
print(json.dumps(results, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -86,6 +86,13 @@ def parse_read(text: str) -> tuple[str, str]:
|
||||
return (speed, confidence) if separator else ("", "")
|
||||
|
||||
|
||||
def nonzero_value(text: str) -> str:
|
||||
try:
|
||||
return text if float(text) > 0.0 else ""
|
||||
except (TypeError, ValueError):
|
||||
return ""
|
||||
|
||||
|
||||
def queue_row(row: dict[str, str]) -> dict[str, str]:
|
||||
key = record_key(row)
|
||||
route, dongle_id, log_id = route_identity(row)
|
||||
@@ -99,8 +106,17 @@ def queue_row(row: dict[str, str]) -> dict[str, str]:
|
||||
read_sources += ";logged_vision_publish"
|
||||
|
||||
review_reasons = ["corrected_source_timing"]
|
||||
review_priority = float(row.get("score") or 0.0)
|
||||
if row.get("event_type", "") == "visionPublish":
|
||||
review_reasons.extend(("route_vision_publish", f"published_{row.get('published_speed', '')}"))
|
||||
published_speed = row.get("published_speed", "")
|
||||
review_reasons.extend(("route_vision_publish", f"published_{published_speed}"))
|
||||
if published_speed and candidate_speed and published_speed != candidate_speed:
|
||||
review_reasons.append("logged_current_value_disagreement")
|
||||
review_priority += 5.0
|
||||
map_speed = nonzero_value(row.get("map_speed", ""))
|
||||
if map_speed and published_speed and map_speed != published_speed:
|
||||
review_reasons.append("logged_map_disagreement")
|
||||
review_priority += 3.0
|
||||
else:
|
||||
review_reasons.append("route_bookmark")
|
||||
|
||||
@@ -129,9 +145,9 @@ def queue_row(row: dict[str, str]) -> dict[str, str]:
|
||||
"read_sources": read_sources,
|
||||
"read_support_count": "1",
|
||||
"is_regulatory": row.get("is_regulatory", ""),
|
||||
"map_current_speed_limit_mph": row.get("map_speed", ""),
|
||||
"map_next_speed_limit_mph": row.get("next_speed", ""),
|
||||
"review_priority": row.get("score", ""),
|
||||
"map_current_speed_limit_mph": nonzero_value(row.get("map_speed", "")),
|
||||
"map_next_speed_limit_mph": nonzero_value(row.get("next_speed", "")),
|
||||
"review_priority": f"{review_priority:.4f}",
|
||||
"review_reasons": ";".join(review_reasons),
|
||||
})
|
||||
return item
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
|
||||
if __package__ in (None, ""):
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from common import EXTENDED_CLASSIFIER_SPEED_VALUES # type: ignore # noqa: TID251
|
||||
else:
|
||||
from .common import EXTENDED_CLASSIFIER_SPEED_VALUES
|
||||
|
||||
|
||||
DEFAULT_CLASSES = tuple(str(value) for value in EXTENDED_CLASSIFIER_SPEED_VALUES) + ("reject",)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Expand a YOLO classification head while preserving existing class rows.")
|
||||
parser.add_argument("--model", type=Path, required=True, help="Existing Ultralytics classification checkpoint.")
|
||||
parser.add_argument("--output", type=Path, required=True, help="Expanded checkpoint path.")
|
||||
parser.add_argument("--classes", nargs="+", default=DEFAULT_CLASSES, help="New output classes in dataset index order.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
from ultralytics import YOLO
|
||||
|
||||
args = parse_args()
|
||||
model = YOLO(str(args.model.expanduser().resolve()))
|
||||
old_names = {int(index): str(name) for index, name in model.names.items()}
|
||||
new_names = dict(enumerate(args.classes))
|
||||
if len(set(new_names.values())) != len(new_names):
|
||||
raise ValueError("--classes contains duplicate names")
|
||||
if not set(old_names.values()).issubset(new_names.values()):
|
||||
missing = sorted(set(old_names.values()) - set(new_names.values()))
|
||||
raise ValueError(f"Expanded classes omit existing outputs: {missing}")
|
||||
|
||||
head = model.model.model[-1]
|
||||
old_linear = head.linear
|
||||
new_linear = torch.nn.Linear(
|
||||
old_linear.in_features,
|
||||
len(new_names),
|
||||
bias=old_linear.bias is not None,
|
||||
device=old_linear.weight.device,
|
||||
dtype=old_linear.weight.dtype,
|
||||
)
|
||||
new_index_by_name = {name: index for index, name in new_names.items()}
|
||||
with torch.no_grad():
|
||||
for old_index, name in old_names.items():
|
||||
new_index = new_index_by_name[name]
|
||||
new_linear.weight[new_index].copy_(old_linear.weight[old_index])
|
||||
if old_linear.bias is not None:
|
||||
new_linear.bias[new_index].copy_(old_linear.bias[old_index])
|
||||
|
||||
head.linear = new_linear
|
||||
head.np = sum(parameter.numel() for parameter in head.parameters())
|
||||
model.model.names = new_names
|
||||
model.model.yaml["nc"] = len(new_names)
|
||||
model.model.args["classes"] = None
|
||||
|
||||
output = args.output.expanduser().resolve()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
model.save(str(output))
|
||||
print(f"Expanded {len(old_names)} outputs to {len(new_names)}: {output}")
|
||||
print(new_names)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -222,7 +222,11 @@ def save_classifier_crop(base_dir: Path, split: str, speed_value: int, image_bgr
|
||||
def collect_backgrounds(background_dir: Path) -> list[Path]:
|
||||
if not background_dir.is_dir():
|
||||
return []
|
||||
return sorted(path for path in background_dir.iterdir() if path.suffix.lower() in {".jpg", ".jpeg", ".png"})
|
||||
return sorted(
|
||||
path
|
||||
for path in background_dir.iterdir()
|
||||
if not path.name.startswith("._") and path.suffix.lower() in {".jpg", ".jpeg", ".png"}
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -233,6 +237,7 @@ def main():
|
||||
parser.add_argument("--val-count", type=int, default=1200, help="Number of synthetic validation detector images.")
|
||||
parser.add_argument("--negative-ratio", type=float, default=0.18, help="Share of detector images with no sign.")
|
||||
parser.add_argument("--speed-values", nargs="+", type=int, default=list(DEFAULT_SPEED_VALUES), help="Posted values to synthesize.")
|
||||
parser.add_argument("--regulatory-only", action="store_true", help="Generate only regulatory signs for the requested values.")
|
||||
parser.add_argument("--seed", type=int, default=20260330, help="Random seed.")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -265,7 +270,11 @@ def main():
|
||||
detector_lines: list[str] = []
|
||||
|
||||
if rng.random() >= args.negative_ratio:
|
||||
sign_spec = choose_sign_spec(rng, speed_values)
|
||||
sign_spec = (
|
||||
SignSpec(detector_class=0, style="regulatory", speed_value=rng.choice(speed_values))
|
||||
if args.regulatory_only
|
||||
else choose_sign_spec(rng, speed_values)
|
||||
)
|
||||
if sign_spec.style == "advisory":
|
||||
sign_image = render_advisory_sign(sign_spec.speed_value or 25, seed=rng.randint(0, 1_000_000))
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
if __package__ in (None, ""):
|
||||
import sys
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from common import EXTENDED_CLASSIFIER_SPEED_VALUES # type: ignore # noqa: TID251
|
||||
else:
|
||||
from .common import EXTENDED_CLASSIFIER_SPEED_VALUES
|
||||
|
||||
|
||||
IMAGE_SUFFIXES = frozenset((".jpg", ".jpeg", ".png", ".bmp", ".webp"))
|
||||
SUPPORTED_CLASSES = frozenset(str(value) for value in EXTENDED_CLASSIFIER_SPEED_VALUES) | {"reject"}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Merge image-folder classifier datasets using hard links when possible.")
|
||||
parser.add_argument("sources", nargs="+", type=Path, help="Classifier roots containing train/ and val/.")
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--overwrite", action="store_true")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def link_or_copy(source: Path, destination: Path) -> None:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
os.link(source, destination)
|
||||
except OSError:
|
||||
shutil.copy2(source, destination)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
sources = [path.expanduser().resolve() for path in args.sources]
|
||||
output = args.output.expanduser().resolve()
|
||||
if output.exists():
|
||||
if not args.overwrite:
|
||||
raise FileExistsError(f"Output already exists: {output}")
|
||||
shutil.rmtree(output)
|
||||
|
||||
counts: Counter[str] = Counter()
|
||||
for source_index, source_root in enumerate(sources):
|
||||
for split in ("train", "val"):
|
||||
split_root = source_root / split
|
||||
if not split_root.is_dir():
|
||||
raise FileNotFoundError(split_root)
|
||||
for class_dir in sorted(split_root.iterdir()):
|
||||
if not class_dir.is_dir() or class_dir.name.startswith("._"):
|
||||
continue
|
||||
if class_dir.name not in SUPPORTED_CLASSES:
|
||||
raise ValueError(f"Unsupported classifier class {class_dir.name!r} in {source_root}")
|
||||
for image_path in sorted(class_dir.iterdir()):
|
||||
if image_path.name.startswith("._") or image_path.suffix.lower() not in IMAGE_SUFFIXES or not image_path.is_file():
|
||||
continue
|
||||
destination = output / split / class_dir.name / f"s{source_index:02d}_{image_path.name}"
|
||||
link_or_copy(image_path, destination)
|
||||
counts[f"{split}/{class_dir.name}"] += 1
|
||||
|
||||
summary = {
|
||||
"sources": [str(path) for path in sources],
|
||||
"output": str(output),
|
||||
"counts": dict(sorted(counts.items())),
|
||||
}
|
||||
(output / "merge_summary.json").write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="ascii")
|
||||
print(json.dumps(summary, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -21,7 +21,7 @@ VIDEO_SUFFIXES = {".hevc", ".mp4", ".mov", ".mkv", ".avi"}
|
||||
|
||||
def iter_source_files(paths: list[Path]):
|
||||
for input_path in paths:
|
||||
if input_path.is_file():
|
||||
if input_path.is_file() and not input_path.name.startswith("._"):
|
||||
yield input_path
|
||||
continue
|
||||
|
||||
@@ -29,7 +29,7 @@ def iter_source_files(paths: list[Path]):
|
||||
continue
|
||||
|
||||
for path in sorted(input_path.rglob("*")):
|
||||
if path.is_file() and path.suffix.lower() in IMAGE_SUFFIXES | VIDEO_SUFFIXES:
|
||||
if path.is_file() and not path.name.startswith("._") and path.suffix.lower() in IMAGE_SUFFIXES | VIDEO_SUFFIXES:
|
||||
yield path
|
||||
|
||||
|
||||
|
||||
@@ -321,19 +321,32 @@ function setSpeed(speed, shouldSave) {
|
||||
|
||||
function handleDigitShortcut(digit) {
|
||||
speedBuffer += digit;
|
||||
if (speedBuffer.length > 2) speedBuffer = speedBuffer.slice(-2);
|
||||
if (speedBuffer.length > 3) speedBuffer = digit;
|
||||
if (speedBufferTimer) clearTimeout(speedBufferTimer);
|
||||
speedBufferTimer = setTimeout(clearSpeedBuffer, 1000);
|
||||
|
||||
if (speedBuffer.length < 2) return;
|
||||
|
||||
if (speedBuffer === "10") {
|
||||
clearTimeout(speedBufferTimer);
|
||||
speedBufferTimer = setTimeout(() => {
|
||||
if (speedBuffer === "10") {
|
||||
clearSpeedBuffer();
|
||||
setSpeed(10, true);
|
||||
}
|
||||
}, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const speed = Number(speedBuffer);
|
||||
clearSpeedBuffer();
|
||||
if (speeds.includes(speed)) {
|
||||
clearSpeedBuffer();
|
||||
setSpeed(speed, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (speedBuffer.length < 3) return;
|
||||
|
||||
speedBuffer = digit;
|
||||
speedBufferTimer = setTimeout(clearSpeedBuffer, 1000);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user