Compare commits
2 Commits
f1f5dcbed7
...
46cf2d5afe
| Author | SHA1 | Date | |
|---|---|---|---|
| 46cf2d5afe | |||
| 98b4d0d200 |
@@ -118,7 +118,7 @@ sagemaker:
|
|||||||
Confirm the CLI can see the configured SageMaker role and S3 bucket:
|
Confirm the CLI can see the configured SageMaker role and S3 bucket:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
qc-cli infra status --config config.yaml
|
qc-cli infra status
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. Upload The Dataset
|
## 4. Upload The Dataset
|
||||||
@@ -153,7 +153,7 @@ Or pass the job name explicitly:
|
|||||||
qc-cli train status qc-cli-YYYYMMDD-HHMMSS
|
qc-cli train status qc-cli-YYYYMMDD-HHMMSS
|
||||||
```
|
```
|
||||||
|
|
||||||
## Outputs
|
## SageMaker Outputs
|
||||||
|
|
||||||
When the job completes, SageMaker packages the files written under `/opt/ml/model` into `model.tar.gz`.
|
When the job completes, SageMaker packages the files written under `/opt/ml/model` into `model.tar.gz`.
|
||||||
|
|
||||||
@@ -167,6 +167,91 @@ metrics.json
|
|||||||
|
|
||||||
The archive is stored under the configured `s3.model_prefix`.
|
The archive is stored under the configured `s3.model_prefix`.
|
||||||
|
|
||||||
|
## 6. Configure Qualcomm AI Hub
|
||||||
|
|
||||||
|
Authenticate with Qualcomm AI Hub:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qai-hub configure --api_token
|
||||||
|
```
|
||||||
|
|
||||||
|
Add AI Hub settings to `config.yaml`. The input name and image size must match the ONNX model exported by this example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
aihub:
|
||||||
|
device:
|
||||||
|
name: Dragonwing IQ-9075 EVK
|
||||||
|
target_runtime: tflite
|
||||||
|
input_specs:
|
||||||
|
images: [[1, 3, 640, 640], float32]
|
||||||
|
job_name: meter-detection
|
||||||
|
model_name: meter-detection
|
||||||
|
output_dir: build/qai-hub/meter-detection
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the same image size configured in `sagemaker.training.hyperparameters.imgsz`. For example, a smoke-test model
|
||||||
|
trained with `imgsz: 320` requires `images: [[1, 3, 320, 320], float32]`.
|
||||||
|
|
||||||
|
## 7. Prepare AI Hub Inputs
|
||||||
|
|
||||||
|
Generate calibration samples and a validation input from the downloaded dataset:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python examples/meter-detection/prepare_aihub_inputs.py --image-size 640
|
||||||
|
```
|
||||||
|
|
||||||
|
This writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
examples/meter-detection/data/aihub_calibration/*.npy
|
||||||
|
examples/meter-detection/data/inputs.npz
|
||||||
|
```
|
||||||
|
|
||||||
|
The script applies the preprocessing expected by the exported YOLO model: aspect-ratio-preserving letterboxing,
|
||||||
|
RGB channel order, channel-first layout, and pixel values normalized to `[0, 1]`.
|
||||||
|
|
||||||
|
Set `--image-size` to the training `imgsz` value when it is not `640`.
|
||||||
|
|
||||||
|
## 8. Upload To Qualcomm AI Hub
|
||||||
|
|
||||||
|
Use the SageMaker job name printed by `qc-cli train start`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qc-cli ai-hub upload \
|
||||||
|
examples/meter-detection/data/aihub_calibration \
|
||||||
|
examples/meter-detection/data/inputs.npz \
|
||||||
|
--from-job qc-cli-YYYYMMDD-HHMMSS
|
||||||
|
```
|
||||||
|
|
||||||
|
The command downloads the job's `model.tar.gz`, finds `model.onnx`, uploads it to AI Hub, and runs quantization,
|
||||||
|
compilation, validation, and profiling. The uploaded source model uses the configured
|
||||||
|
`aihub.model_name`.
|
||||||
|
|
||||||
|
If the meter-detection job is still the last training job in `.qc-cli.json`, `--from-job` can be omitted. Keeping it
|
||||||
|
explicit prevents accidentally uploading an artifact from a different training run.
|
||||||
|
|
||||||
|
To resume after a completed step, use one of:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qc-cli ai-hub upload \
|
||||||
|
examples/meter-detection/data/aihub_calibration \
|
||||||
|
examples/meter-detection/data/inputs.npz \
|
||||||
|
--from-step compile
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qc-cli ai-hub upload \
|
||||||
|
examples/meter-detection/data/aihub_calibration \
|
||||||
|
examples/meter-detection/data/inputs.npz \
|
||||||
|
--from-step validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Download the compiled artifact after the workflow completes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qc-cli ai-hub download --output build/qai-hub/meter-detection/model.tflite
|
||||||
|
```
|
||||||
|
|
||||||
## Training Hyperparameters
|
## Training Hyperparameters
|
||||||
|
|
||||||
Values under `sagemaker.training.hyperparameters` are passed to `source/train.py` as command-line arguments.
|
Values under `sagemaker.training.hyperparameters` are passed to `source/train.py` as command-line arguments.
|
||||||
|
|||||||
92
examples/meter-detection/prepare_aihub_inputs.py
Normal file
92
examples/meter-detection/prepare_aihub_inputs.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Prepare Qualcomm AI Hub calibration and validation inputs for the meter detector."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png"}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dataset-dir",
|
||||||
|
type=Path,
|
||||||
|
default=Path("examples/meter-detection/data/electric-meter-detection"),
|
||||||
|
help="Root of the extracted Roboflow dataset.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--calibration-dir",
|
||||||
|
type=Path,
|
||||||
|
default=Path("examples/meter-detection/data/aihub_calibration"),
|
||||||
|
help="Directory where .npy calibration samples will be written.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--input-file",
|
||||||
|
type=Path,
|
||||||
|
default=Path("examples/meter-detection/data/inputs.npz"),
|
||||||
|
help="Validation .npz input file for qc-cli ai-hub validate.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--input-name", default="images", help="ONNX input name.")
|
||||||
|
parser.add_argument("--image-size", type=int, default=640, help="Square image size used for ONNX export.")
|
||||||
|
parser.add_argument("--samples", type=int, default=16, help="Number of calibration samples to write.")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_image(path: Path, image_size: int) -> np.ndarray:
|
||||||
|
"""Apply Ultralytics-style letterboxing and produce an NCHW float32 tensor."""
|
||||||
|
with Image.open(path) as source:
|
||||||
|
image = source.convert("RGB")
|
||||||
|
|
||||||
|
scale = min(image_size / image.width, image_size / image.height)
|
||||||
|
resized_width = round(image.width * scale)
|
||||||
|
resized_height = round(image.height * scale)
|
||||||
|
image = image.resize((resized_width, resized_height), Image.Resampling.BILINEAR)
|
||||||
|
|
||||||
|
canvas = Image.new("RGB", (image_size, image_size), (114, 114, 114))
|
||||||
|
left = round((image_size - resized_width) / 2 - 0.1)
|
||||||
|
top = round((image_size - resized_height) / 2 - 0.1)
|
||||||
|
canvas.paste(image, (left, top))
|
||||||
|
|
||||||
|
array = np.asarray(canvas, dtype=np.float32) / 255.0
|
||||||
|
return np.transpose(array, (2, 0, 1))[None, ...].astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
if args.image_size < 1:
|
||||||
|
raise SystemExit("--image-size must be at least 1")
|
||||||
|
if args.samples < 1:
|
||||||
|
raise SystemExit("--samples must be at least 1")
|
||||||
|
|
||||||
|
images = sorted(
|
||||||
|
path
|
||||||
|
for path in args.dataset_dir.rglob("*")
|
||||||
|
if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS and path.parent.name == "images"
|
||||||
|
)
|
||||||
|
if not images:
|
||||||
|
raise SystemExit(f"No images found under {args.dataset_dir}")
|
||||||
|
|
||||||
|
args.calibration_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.input_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
for stale_sample in args.calibration_dir.glob("sample_*.npy"):
|
||||||
|
stale_sample.unlink()
|
||||||
|
|
||||||
|
prepared: list[np.ndarray] = []
|
||||||
|
for index, image_path in enumerate(images[: args.samples]):
|
||||||
|
sample = preprocess_image(image_path, args.image_size)
|
||||||
|
np.save(args.calibration_dir / f"sample_{index:03d}.npy", sample)
|
||||||
|
prepared.append(sample)
|
||||||
|
|
||||||
|
np.savez(args.input_file, **{args.input_name: prepared[0]}) # pyright: ignore[reportArgumentType]
|
||||||
|
print(f"Wrote {len(prepared)} calibration samples to {args.calibration_dir}")
|
||||||
|
print(f"Wrote validation input to {args.input_file}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user