diff --git a/CNN-examples/getting_started_resnet/int8/Dockerfile b/CNN-examples/getting_started_resnet/int8/Dockerfile
new file mode 100644
index 00000000..ea758056
--- /dev/null
+++ b/CNN-examples/getting_started_resnet/int8/Dockerfile
@@ -0,0 +1,44 @@
+# ==============================================================================
+# Dockerfile: AMD Quark Quantization Pipeline for ResNet INT8
+# MLOps Decoupled Build Environment (Google Cloud Console / Linux / Docker)
+# ==============================================================================
+# Purpose:
+# Isolates the AMD Quark Quantization and PyTorch-to-ONNX export pipeline
+# from the host Ryzen AI runtime environment. This completely avoids the
+# Python package dependency conflicts with 'flexml 1.8.0' (Issue #385).
+#
+# Volume Mount Contract:
+# - /app/models : Mounted from host to persist exported ONNX & quantized models
+# - /app/data : Mounted from host to cache downloaded CIFAR-10 datasets
+# ==============================================================================
+
+FROM python:3.10-slim
+
+ENV PYTHONUNBUFFERED=1 \
+ DEBIAN_FRONTEND=noninteractive
+
+WORKDIR /app
+
+# Install system dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ curl \
+ wget \
+ git \
+ libgl1 \
+ libglib2.0-0 \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install exact pinned Python quantization dependencies for reproducibility
+COPY requirements_quantize.txt .
+RUN pip install --no-cache-dir --upgrade pip && \
+ pip install --no-cache-dir -r requirements_quantize.txt
+
+# Copy source scripts
+COPY resnet_utils.py prepare_model_data.py resnet_quantize.py ./
+
+# Create explicit directories for host volume mounts
+RUN mkdir -p /app/data /app/models
+
+# Entry command: prepare data, export ONNX, and run Quark INT8 quantization
+CMD ["sh", "-c", "python prepare_model_data.py && python resnet_quantize.py && echo '=== Quantization Complete! Artifact: models/resnet_quantized.onnx ==='"]
diff --git a/CNN-examples/getting_started_resnet/int8/Readme.md b/CNN-examples/getting_started_resnet/int8/Readme.md
index b39b3c83..25f06163 100644
--- a/CNN-examples/getting_started_resnet/int8/Readme.md
+++ b/CNN-examples/getting_started_resnet/int8/Readme.md
@@ -1,12 +1,162 @@
-  Ryzen™ AI ResNet Tutorial
+ |  Ryzen™ AI ResNet INT8 Tutorial
|
-# Getting Started Example
+# Getting Started ResNet with INT8 Quantization & NPU Deployment
-This tutorial uses a fine-tuned version of the ResNet model (using the CIFAR-10 dataset) to demonstrate the process of preparing, quantizing, and deploying a model using Ryzen AI Software. The tutorial features deployment using both Python and C++ ONNX runtime code.
+This tutorial demonstrates how to prepare, quantize, and deploy a fine-tuned **ResNet-50** model on CIFAR-10 using **AMD Quark** and **Ryzen AI Software 1.8** with ONNX Runtime on the NPU.
-For a walkthrough of this tutorial please follow the [tutorial documentation](https://ryzenai.docs.amd.com/en/latest/getstartex.html)
+---
+
+## ⚠️ Important Notice on Issue #385 & Dependency Isolation
+
+In **Ryzen AI 1.8**, AMD unbundled the Quark quantization toolkit from the default installation. Running `pip install` for Quark inside the active `ryzen-ai-1.8.0` environment creates severe Python package dependency conflicts:
+- `flexml 1.8.0` strictly requires `torch==2.6.0+cpu`, `torchvision==0.21.0+cpu`, and `onnx==1.21.0`.
+- AMD Quark requires newer or unpinned versions of `torch`, `torchvision`, and `onnx`.
+- Installing both into the same environment breaks `flexml`, causing NPU execution (`predict.py --ep npu`) to fail.
+
+### MLOps Best Practice: Decoupled Build vs. Runtime Pipeline
+
+```
+┌────────────────────────────────────────────────────────┐
+│ Phase 1: Build & Quantization │
+│ (Docker / Google Cloud Console / Isolated Conda) │
+│ │
+│ - Export ResNet PyTorch checkpoint -> ONNX │
+│ - AMD Quark Calibration & INT8 Quantization │
+│ - Output Artifact: models/resnet_quantized.onnx │
+└──────────────────────────┬─────────────────────────────┘
+ │
+ │ Transfer Artifact
+ ▼
+┌────────────────────────────────────────────────────────┐
+│ Phase 2: Edge NPU Serving │
+│ (Local Ryzen AI 1.8 Host) │
+│ │
+│ - ONNX Runtime with VitisAIExecutionProvider (flexml) │
+│ - Minimal runtime dependencies (OpenCV, Pillow) │
+│ - Zero pollution of flexml pinned packages │
+└──────────────────────────┘
+```
+
+---
+
+## Option A: Docker / Google Cloud Console (Recommended)
+
+Using Docker keeps the quantization pipeline 100% reproducible and isolated from your host OS. You can run this in **Google Cloud Console (Cloud Shell / GCP VM)** or **Docker Desktop**.
+
+### Step 1: Run Quantization in Docker / GCP Cloud Shell
+Navigate to this directory and run:
+
+**On Linux / Google Cloud Console / WSL:**
+```bash
+chmod +x docker_run.sh
+./docker_run.sh
+```
+
+**On Windows (Docker Desktop):**
+```cmd
+docker_run.bat
+```
+
+**Or run Docker manually:**
+```bash
+# 1. Build the image
+docker build -t ryzenai-resnet-quark:latest -f Dockerfile .
+
+# 2. Run the container with volume mounts
+docker run --rm \
+ -v "$(pwd)/models:/app/models" \
+ -v "$(pwd)/data:/app/data" \
+ ryzenai-resnet-quark:latest
+```
+
+This will automatically:
+1. Download CIFAR-10 data and the pre-trained ResNet-50 checkpoint (via Git LFS URL).
+2. Export `models/resnet_trained_for_cifar10.onnx`.
+3. Quantize the model using Quark into `models/resnet_quantized.onnx`.
+
+### Step 2: Transfer Quantized Model to Ryzen AI Host
+If you ran Docker in Google Cloud Console:
+- Download the generated file `models/resnet_quantized.onnx` to your local machine (place it in `CNN-examples/getting_started_resnet/int8/models/`).
+
+---
+
+## Option B: Dual Conda Environments (Local Windows without Docker)
+
+If you prefer running locally on Windows without Docker, use two separate Conda environments.
+
+### Step 1: Quantization Environment (Quark)
+```cmd
+# Create an isolated environment for Quark
+conda create -n quark_env python=3.10 -y
+conda activate quark_env
+
+# Install quantization dependencies
+pip install -r requirements_quantize.txt
+
+# Export model to ONNX & Quantize
+python prepare_model_data.py
+python resnet_quantize.py
+
+# Deactivate when done
+conda deactivate
+```
+
+---
+
+## Phase 2: Inference & Deployment on Ryzen AI NPU
+
+Now that `models/resnet_quantized.onnx` is ready, run inference using your official `ryzen-ai-1.8.0` environment:
+
+### Step 1: Activate Ryzen AI Environment
+```cmd
+conda activate ryzen-ai-1.8.0
+```
+
+### Step 2: Install Runtime Inference Requirements
+```cmd
+pip install -r requirements_infer.txt
+```
+*(Notice: This only installs `opencv-python` and `Pillow`, keeping `flexml 1.8.0` completely intact!)*
+
+### Step 3: Run Inference
+
+**On NPU:**
+```cmd
+python predict.py --ep npu
+```
+
+**On CPU (Fallback):**
+```cmd
+python predict.py --ep cpu
+```
+
+### Step 4: C++ Inference (Optional)
+Navigate to `cpp/resnet_cifar`:
+```cmd
+cd cpp/resnet_cifar
+mkdir build && cd build
+cmake .. -A x64 -DCMAKE_PREFIX_PATH="%RYZEN_AI_INSTALLATION_PATH%"
+cmake --build . --config Release
+cd Release
+resnet_cifar.exe -m ..\..\..\models\resnet_quantized.onnx -d ..\..\..\data\cifar-10-batches-bin
+```
+
+---
+
+## File Overview
+
+| File | Purpose |
+| :--- | :--- |
+| `Dockerfile` | Container configuration for offline Quark quantization |
+| `docker_run.sh` / `docker_run.bat` | One-click script to build & run Docker quantization |
+| `requirements_quantize.txt` | Dependencies for Model Export & Quark INT8 quantization |
+| `requirements_infer.txt` | Dependencies for NPU runtime inference on Ryzen AI host |
+| `prepare_model_data.py` | Downloads CIFAR-10 data, pre-trained weights, and exports ONNX |
+| `resnet_quantize.py` | Runs AMD Quark calibration and produces `resnet_quantized.onnx` |
+| `predict.py` | Runs inference on Ryzen AI NPU using ONNX Runtime VitisAI EP |
+| `resnet_utils.py` | Helper functions for NPU device detection and paths |
diff --git a/CNN-examples/getting_started_resnet/int8/docker_run.bat b/CNN-examples/getting_started_resnet/int8/docker_run.bat
new file mode 100644
index 00000000..0b35dde5
--- /dev/null
+++ b/CNN-examples/getting_started_resnet/int8/docker_run.bat
@@ -0,0 +1,37 @@
+@echo off
+REM ==============================================================================
+REM AMD Quark Quantization Pipeline Runner (Windows Docker)
+REM ==============================================================================
+setlocal enabledelayedexpansion
+
+echo ==================================================================
+echo Building Docker image: ryzenai-resnet-quark
+echo ==================================================================
+docker build -t ryzenai-resnet-quark:latest -f Dockerfile .
+if %errorlevel% neq 0 (
+ echo Error: Docker build failed!
+ exit /b %errorlevel%
+)
+
+echo ==================================================================
+echo Running Quantization Pipeline in Container
+echo ==================================================================
+if not exist "models" mkdir models
+if not exist "data" mkdir data
+
+docker run --rm ^
+ -v "%cd%\models:/app/models" ^
+ -v "%cd%\data:/app/data" ^
+ ryzenai-resnet-quark:latest
+
+if %errorlevel% neq 0 (
+ echo Error: Quantization failed!
+ exit /b %errorlevel%
+)
+
+echo ==================================================================
+echo SUCCESS!
+echo Quantized model generated at: %cd%\models\resnet_quantized.onnx
+echo You can now run predict.py in your ryzen-ai conda environment.
+echo ==================================================================
+
diff --git a/CNN-examples/getting_started_resnet/int8/docker_run.sh b/CNN-examples/getting_started_resnet/int8/docker_run.sh
new file mode 100644
index 00000000..9b01bb34
--- /dev/null
+++ b/CNN-examples/getting_started_resnet/int8/docker_run.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# ==============================================================================
+# AMD Quark Quantization Pipeline Runner (Docker / Google Cloud Console)
+# ==============================================================================
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$SCRIPT_DIR"
+
+echo "=================================================================="
+echo " Building Docker image: ryzenai-resnet-quark"
+echo "=================================================================="
+docker build -t ryzenai-resnet-quark:latest -f Dockerfile .
+
+echo "=================================================================="
+echo " Running Quantization Pipeline in Container"
+echo " Mounts local 'models' and 'data' directories"
+echo "=================================================================="
+mkdir -p models data
+docker run --rm \
+ -v "${PWD}/models:/app/models" \
+ -v "${PWD}/data:/app/data" \
+ ryzenai-resnet-quark:latest
+
+echo "=================================================================="
+echo " SUCCESS!"
+echo " Quantized model generated at: ${PWD}/models/resnet_quantized.onnx"
+echo " You can now transfer this model to your Ryzen AI PC for NPU inference."
+echo "=================================================================="
+
diff --git a/CNN-examples/getting_started_resnet/int8/prepare_model_data.py b/CNN-examples/getting_started_resnet/int8/prepare_model_data.py
index 5f8e6c0d..5c11b840 100644
--- a/CNN-examples/getting_started_resnet/int8/prepare_model_data.py
+++ b/CNN-examples/getting_started_resnet/int8/prepare_model_data.py
@@ -3,13 +3,71 @@
# Licensed under the MIT License.
# --------------------------------------------------------------------------
import argparse
+import hashlib
+import os
import random
-import ssl
import sys
import tarfile
import urllib.request
-ssl._create_default_https_context = ssl._create_unverified_context
+EXPECTED_CHECKPOINT_SHA256 = "6fbc917577ccf951e7c6039e3d85e95b11162abc06f9a69fe9ef26a5178d00cd"
+CHECKPOINT_LFS_URL = os.environ.get(
+ "RYZENAI_RESNET_CHECKPOINT_URL",
+ "https://media.githubusercontent.com/media/amd/RyzenAI-SW/main/CNN-examples/getting_started_resnet/int8/models/resnet_trained_for_cifar10.pt"
+)
+
+
+def compute_sha256(filepath):
+ sha256_hash = hashlib.sha256()
+ with open(filepath, "rb") as f:
+ for byte_block in iter(lambda: f.read(65536), b""):
+ sha256_hash.update(byte_block)
+ return sha256_hash.hexdigest().lower()
+
+
+def is_git_lfs_pointer(filepath):
+ """Detects Git-LFS pointer signature: 'version https://git-lfs.github.com/spec/v1'"""
+ if not filepath.exists():
+ return False, None
+ if filepath.stat().st_size > 1024:
+ return False, None
+ try:
+ content = filepath.read_text(encoding="utf-8")
+ if "version https://git-lfs.github.com/spec/v1" in content:
+ for line in content.splitlines():
+ if line.startswith("oid sha256:"):
+ return True, line.split("oid sha256:")[1].strip()
+ return True, None
+ except Exception:
+ pass
+ return False, None
+
+
+def verify_or_download_checkpoint(model_pt_path):
+ """Ensures authoritative pre-trained checkpoint is present and verified by SHA-256."""
+ is_lfs, pointer_sha = is_git_lfs_pointer(model_pt_path)
+
+ # Check for consistency if local pointer exists
+ if is_lfs and pointer_sha and pointer_sha.lower() != EXPECTED_CHECKPOINT_SHA256.lower():
+ raise RuntimeError(
+ f"Security Error: Repository Git-LFS pointer digest ({pointer_sha}) does not match "
+ f"authoritative checkpoint digest ({EXPECTED_CHECKPOINT_SHA256})."
+ )
+
+ if not model_pt_path.exists() or is_lfs:
+ print("Pre-trained checkpoint is an LFS pointer or missing. Downloading from Git LFS...")
+ print(f"Downloading from: {CHECKPOINT_LFS_URL}")
+ urllib.request.urlretrieve(CHECKPOINT_LFS_URL, model_pt_path, reporthook=download_progress)
+
+ actual_sha = compute_sha256(model_pt_path)
+ if actual_sha != EXPECTED_CHECKPOINT_SHA256.lower():
+ raise RuntimeError(
+ f"Security/Integrity Error: SHA-256 checksum mismatch for {model_pt_path}!\n"
+ f"Expected: {EXPECTED_CHECKPOINT_SHA256}\n"
+ f"Actual : {actual_sha}\n"
+ f"The downloaded file may be corrupted or compromised."
+ )
+ print(f"Verified checkpoint SHA-256 digest: {actual_sha} (Integrity OK)")
def download_progress(block_num, block_size, total_size):
@@ -171,9 +229,13 @@ def main():
print(f"Extracting {label}...")
with tarfile.open(path) as f:
f.extractall(data_dir)
+ model_pt_path = models_dir / "resnet_trained_for_cifar10.pt"
if args.train:
prepare_model(args.num_epochs, models_dir, data_dir)
- model = torch.load(str(models_dir / "resnet_trained_for_cifar10.pt"), weights_only=False)
+ else:
+ verify_or_download_checkpoint(model_pt_path)
+
+ model = torch.load(str(model_pt_path), weights_only=False)
export_to_onnx(model, models_dir)
diff --git a/CNN-examples/getting_started_resnet/int8/requirements.txt b/CNN-examples/getting_started_resnet/int8/requirements.txt
index bad94a5b..a67e8631 100644
--- a/CNN-examples/getting_started_resnet/int8/requirements.txt
+++ b/CNN-examples/getting_started_resnet/int8/requirements.txt
@@ -1,5 +1,22 @@
-amd-quark==0.11
-torchvision==0.23.0
-opencv-python==4.11.0.86
-numpy==1.26.4
+# ==============================================================================
+# Ryzen AI ResNet INT8 Tutorial Requirements
+# ==============================================================================
+# WARNING / IMPORTANT NOTICE (Issue #385):
+# AMD Quark is unbundled from Ryzen AI 1.8. Installing 'amd-quark' or
+# conflicting 'torchvision' directly into your active 'ryzen-ai-1.8.0' environment
+# will break 'flexml 1.8.0' (which requires torch==2.6.0+cpu & onnx==1.21.0).
+#
+# Please use the decoupled requirement files based on the stage:
+#
+# 1. QUANTIZATION STAGE (Docker / Google Cloud Console / Isolated Conda):
+# Use: requirements_quantize.txt
+# Or run the pre-configured Docker container:
+# bash docker_run.sh (Linux / Google Cloud Console / WSL)
+# docker_run.bat (Windows)
+#
+# 2. RUNTIME INFERENCE STAGE (Ryzen AI Host Environment):
+# In your active 'ryzen-ai-1.8.0' environment, run:
+# pip install -r requirements_infer.txt
+# ==============================================================================
+-r requirements_infer.txt
diff --git a/CNN-examples/getting_started_resnet/int8/requirements_infer.txt b/CNN-examples/getting_started_resnet/int8/requirements_infer.txt
new file mode 100644
index 00000000..d91e80ca
--- /dev/null
+++ b/CNN-examples/getting_started_resnet/int8/requirements_infer.txt
@@ -0,0 +1,20 @@
+# ==============================================================================
+# Phase 2: Runtime Inference Requirements (Ryzen AI Host Environment)
+# ==============================================================================
+# Hard Prerequisites (Pre-installed in official Ryzen AI 1.8 conda environment):
+# - python (3.9 / 3.10)
+# - flexml==1.8.0 (AMD NPU compiler & execution provider bridge)
+# - torch==2.6.0+cpu
+# - torchvision==0.21.0+cpu
+# - onnx==1.21.0
+# - onnxruntime (with VitisAIExecutionProvider)
+#
+# Runtime dependencies for predict.py:
+# Explicitly pinning 'numpy==1.26.4' ensures that installing opencv-python
+# does not transitively upgrade to NumPy 2.x, which is incompatible with
+# flexml 1.8.0 and PyTorch 2.6.
+# ==============================================================================
+
+numpy==1.26.4
+opencv-python>=4.8.0
+Pillow>=9.0.0
diff --git a/CNN-examples/getting_started_resnet/int8/requirements_quantize.txt b/CNN-examples/getting_started_resnet/int8/requirements_quantize.txt
new file mode 100644
index 00000000..cfa20a23
--- /dev/null
+++ b/CNN-examples/getting_started_resnet/int8/requirements_quantize.txt
@@ -0,0 +1,17 @@
+# ==============================================================================
+# Phase 1: Quantization & Build Pipeline Requirements (Offline / Docker / GCP)
+# ==============================================================================
+# 100% exact pinned dependency set tested for AMD Quark INT8 quantization.
+# Locking all versions ensures fully reproducible, hermetic builds.
+#
+# Tested environment: Python 3.10 (Linux x86_64)
+# ==============================================================================
+
+amd-quark==0.11.2
+torch==2.6.0
+torchvision==0.21.0
+onnx==1.19.0
+onnxruntime==1.23.2
+numpy==1.26.4
+Pillow==10.4.0
+tqdm==4.67.1
diff --git a/CNN-examples/getting_started_resnet/int8/resnet_quantize.py b/CNN-examples/getting_started_resnet/int8/resnet_quantize.py
index 3b451283..eff62bc5 100644
--- a/CNN-examples/getting_started_resnet/int8/resnet_quantize.py
+++ b/CNN-examples/getting_started_resnet/int8/resnet_quantize.py
@@ -25,9 +25,8 @@ def __init__(
self.setup("fit")
def setup(self, stage: str):
- transform = transforms.Compose(
- [transforms.Pad(4), transforms.RandomHorizontalFlip(), transforms.RandomCrop(32), transforms.ToTensor()]
- )
+ # Use deterministic transform for calibration/validation (avoid random flips and crops)
+ transform = transforms.Compose([transforms.ToTensor()])
self.train_dataset = CIFAR10(root=self.train_path, train=True, transform=transform, download=False)
self.val_dataset = CIFAR10(root=self.vld_path, train=True, transform=transform, download=False)
@@ -48,7 +47,11 @@ def __getitem__(self, index):
def create_dataloader(data_dir, batch_size):
cifar10_dataset = CIFAR10DataSet(data_dir)
- _, val_set = torch.utils.data.random_split(cifar10_dataset.val_dataset, [49000, 1000])
+ # Set explicit random seed generator for deterministic calibration split across runs
+ generator = torch.Generator().manual_seed(0)
+ _, val_set = torch.utils.data.random_split(
+ cifar10_dataset.val_dataset, [49000, 1000], generator=generator
+ )
benchmark_dataloader = DataLoader(PytorchResNetDataset(val_set), batch_size=batch_size, drop_last=True)
return benchmark_dataloader