diff --git a/agent-skills/quark-install/SKILL.md b/agent-skills/quark-install/SKILL.md new file mode 100644 index 0000000..2d8eb42 --- /dev/null +++ b/agent-skills/quark-install/SKILL.md @@ -0,0 +1,67 @@ +--- +name: quark-install +description: Install, verify, or repair AMD Quark environments for PyTorch or ONNX workflows. Use for amd-quark setup, Python and accelerator compatibility checks, ROCm/CUDA/CPU package selection, Quark import failures, ONNX Runtime provider selection, or first-run kernel and custom-operator compilation. Inspect first and require confirmation before changing packages or system dependencies. +--- + +# Install AMD Quark + +Prepare a reproducible Quark environment without guessing the user's accelerator, Python environment, or package source. This skill is self-contained: do not delegate its steps to another skill. + +## Workflow + +1. Clarify the intended flow: PyTorch, ONNX-to-ONNX, or both. +2. Collect facts before proposing commands: + + ```bash + python scripts/collect_environment.py --output env_context.json + ``` + + Also ask which Python environment may be modified and whether CPU fallback is acceptable. +3. Read [references/install-options.md](references/install-options.md). Recheck the release-matched [official installation guide](https://quark.docs.amd.com/latest/install.html) when generating commands; package and accelerator matrices change. +4. Present one installation plan containing: + - environment name and Python executable; + - PyTorch or ONNX Runtime variant and index; + - Quark wheel source; + - compiler or first-import requirements; + - exact install and verification commands. +5. Get explicit confirmation before installing, uninstalling, upgrading, compiling, or modifying system packages. +6. Execute only the approved commands. Capture command output and package versions. +7. Run the relevant verification checks from [references/verification-and-recovery.md](references/verification-and-recovery.md). +8. Write `quark_install_result.json` with status, versions, backend, commands run, and per-check results. + +## Decision rules + +- Prefer the universal PyPI wheel when compatibility is uncertain. Use a pre-built AMD-index wheel only after confirming its Python, PyTorch, OS, and accelerator match. +- Install a GPU-enabled PyTorch build from the backend-specific PyTorch index. Do not use bare `pip install torch` for an intended GPU environment. +- Treat absent GPU tools as `unknown`, not automatically as CPU-only. Ask before choosing CPU. +- Never mix a CUDA PyTorch build with a ROCm environment, or the reverse. +- Install exactly one ONNX Runtime variant. On current ROCm 7.x guidance, ONNX Runtime may use the CPU package; explain that trade-off instead of inventing an unsupported ROCm wheel. +- Do not claim success from `pip` alone. Imports and backend/provider checks must pass. +- Do not silently repair an existing environment. Show the proposed uninstall/reinstall sequence and its impact first. + +## Result contract + +Record enough evidence to reproduce or diagnose the environment: + +```json +{ + "status": "ok", + "python": "3.13.0", + "environment": "/path/to/python", + "accelerator": "amd-rocm", + "packages": {"amd-quark": "0.12.0", "torch": "..."}, + "verification": { + "quark_import": "pass", + "torch_backend": "pass", + "torch_kernel": "not-run", + "onnx_custom_ops": "not-requested" + }, + "commands": [] +} +``` + +Use `failed` when a required check fails and `partial` when optional compilation or a requested backend check remains unverified. Include the exact failing command and error excerpt. + +## Stop conditions + +Stop before mutation when the active environment is ambiguous, the requested backend conflicts with detected packages, required credentials are missing, or a wheel compatibility cannot be established. Return the evidence collected and the smallest question or manual check needed to proceed. diff --git a/agent-skills/quark-install/agents/openai.yaml b/agent-skills/quark-install/agents/openai.yaml new file mode 100644 index 0000000..228afb6 --- /dev/null +++ b/agent-skills/quark-install/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Install AMD Quark" + short_description: "Plan, install, and verify a Quark environment" diff --git a/agent-skills/quark-install/references/install-options.md b/agent-skills/quark-install/references/install-options.md new file mode 100644 index 0000000..04e6da0 --- /dev/null +++ b/agent-skills/quark-install/references/install-options.md @@ -0,0 +1,53 @@ +# Installation options + +Use this as a planning checklist, then verify current commands against the official installation guide for the Quark release the user will run. + +## Environment prerequisites + +- Quark 0.12 documentation supports Python 3.11, 3.12, and 3.13; Python 3.14 is not supported. +- PyTorch 2.2 or later is required by the release documentation. Accelerator-specific combinations are narrower, so consult the release's `tools/ci/install_torch.sh` when working from a Quark checkout. +- The universal Quark wheel compiles optional fast kernels or ONNX custom operators on first import. Linux needs a C++ compiler such as `g++`; GPU compilation also needs `hipcc` or `nvcc` and its toolkit path. +- Pre-built Quark wheels require a compatible Python, PyTorch, operating system, and accelerator combination. + +## Quark package source + +| Choice | When to use | Command pattern | +|---|---|---| +| Universal PyPI wheel | Default and widest compatibility | `pip install amd-quark` | +| Pre-built CPU | Matching supported Python and PyTorch; avoid first-run compile | `pip install amd-quark --extra-index-url https://pypi.amd.com/quark/cpu/simple` | +| Pre-built CUDA 12.8 | Matching CUDA/PyTorch environment | `pip install amd-quark --extra-index-url https://pypi.amd.com/quark/cu128/simple` | +| Pre-built ROCm 7.1 | Linux and matching ROCm/PyTorch environment | `pip install amd-quark --extra-index-url https://pypi.amd.com/quark/rocm71/simple` | +| Pre-built ROCm 7.2 | Linux and matching ROCm/PyTorch environment | `pip install amd-quark --extra-index-url https://pypi.amd.com/quark/rocm72/simple` | + +These are release-0.12-era options, not a permanent compatibility promise. If a requested combination is absent from the current official selector, use the universal wheel or stop and explain the gap. + +## PyTorch backend + +Use the [PyTorch installation selector](https://pytorch.org/get-started/locally/) or Quark's release-matched CI matrix. The command pattern is: + +```bash +# ROCm: use the confirmed rocmX.Y tag +pip install torch torchvision --index-url https://download.pytorch.org/whl/rocmX.Y + +# CUDA: use the confirmed cuXYZ tag +pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cuXYZ + +# Explicit CPU environment +pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu +``` + +Never substitute a remembered tag for a checked compatibility matrix. + +## ONNX Runtime + +Quark 0.12 documentation requires ONNX Runtime `>=1.22.2,<=1.25.1`. + +```bash +# CPU, including the documented ROCm 7.x fallback +pip install "onnxruntime>=1.22.2,<=1.25.1" + +# CUDA when the current compatibility guidance supports it +pip install "onnxruntime-gpu>=1.22.2,<=1.25.1" +``` + +Before changing variants, inspect `python -m pip list` and require approval for removing conflicting `onnxruntime*` packages. diff --git a/agent-skills/quark-install/references/verification-and-recovery.md b/agent-skills/quark-install/references/verification-and-recovery.md new file mode 100644 index 0000000..186afc4 --- /dev/null +++ b/agent-skills/quark-install/references/verification-and-recovery.md @@ -0,0 +1,43 @@ +# Verification and recovery + +## Required checks + +Run checks with the exact Python executable used for installation. + +```bash +python -c "import quark; print(quark.__version__)" +python -c "import torch; print(torch.__version__, torch.version.cuda, torch.version.hip); print(torch.cuda.is_available(), torch.cuda.device_count())" +``` + +For a PyTorch flow, optionally force first-run kernel compilation when requested: + +```bash +python -c "import quark.torch.kernel; print('Quark Torch kernels OK')" +``` + +For an ONNX flow: + +```bash +python -c "import onnxruntime as ort; print(ort.__version__, ort.get_available_providers())" +python -c "import quark.onnx; print('quark.onnx OK')" +python -c "import quark.onnx.operators.custom_ops; print('Quark ONNX custom ops OK')" +``` + +The custom-op check may compile code on first import. Treat it as a mutation/cost-bearing step and confirm first. + +## Common recovery paths + +| Evidence | Likely cause | Next action | +|---|---|---| +| `No module named quark` | Wrong environment or install failed | Compare `which python` with `python -m pip --version`; reinstall only after confirmation | +| `torch.cuda.is_available()` is false on a requested GPU | CPU wheel or backend mismatch | Inspect `torch.version.cuda` and `torch.version.hip`; select a release-matched backend wheel | +| Torch kernel import cannot compile | Compiler/toolkit unavailable | Check `g++`, `hipcc`/`nvcc`, `ROCM_PATH`/`CUDA_HOME`; consider a compatible pre-built wheel | +| ONNX provider missing | Wrong ORT variant or unsupported backend | Inspect installed `onnxruntime*` distributions and current provider support | +| ONNX custom-op symbol/ABI error | ORT changed after compilation | Confirm compatible ORT pin, clear only the identified build cache, and rebuild after approval | +| Python is outside the supported range | Unsupported dependency set | Create a fresh Python 3.11-3.13 environment; do not force incompatible pins into the old one | + +When recovery would replace packages, preserve a snapshot first: + +```bash +python -m pip freeze > quark-environment-before.txt +``` diff --git a/agent-skills/quark-install/scripts/collect_environment.py b/agent-skills/quark-install/scripts/collect_environment.py new file mode 100755 index 0000000..cdbd9de --- /dev/null +++ b/agent-skills/quark-install/scripts/collect_environment.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved. +# SPDX-License-Identifier: MIT + +"""Collect non-mutating facts needed to plan an AMD Quark installation.""" + +from __future__ import annotations + +import argparse +import importlib.metadata +import json +import os +import platform +import shutil +import subprocess +import sys +from pathlib import Path + + +def distribution_version(*names: str) -> str | None: + for name in names: + try: + return importlib.metadata.version(name) + except importlib.metadata.PackageNotFoundError: + continue + return None + + +def command_output(command: list[str]) -> str | None: + if not shutil.which(command[0]): + return None + try: + result = subprocess.run(command, capture_output=True, text=True, timeout=10, check=False) + except (OSError, subprocess.TimeoutExpired): + return None + text = (result.stdout or result.stderr).strip() + return text[:2000] if text else None + + +def collect() -> dict[str, object]: + packages = { + "amd-quark": distribution_version("amd-quark"), + "torch": distribution_version("torch"), + "onnx": distribution_version("onnx"), + "onnxruntime": distribution_version("onnxruntime"), + "onnxruntime-gpu": distribution_version("onnxruntime-gpu"), + "onnxruntime-rocm": distribution_version("onnxruntime-rocm", "onnxruntime_rocm"), + } + tools = {name: shutil.which(name) for name in ("g++", "hipcc", "nvcc", "rocm-smi", "nvidia-smi")} + return { + "python": { + "version": platform.python_version(), + "executable": sys.executable, + "implementation": platform.python_implementation(), + "virtual_env": os.environ.get("VIRTUAL_ENV"), + "conda_prefix": os.environ.get("CONDA_PREFIX"), + }, + "platform": {"system": platform.system(), "release": platform.release(), "machine": platform.machine()}, + "environment": { + key: os.environ.get(key) + for key in ("ROCM_PATH", "HIP_VISIBLE_DEVICES", "CUDA_HOME", "CUDA_VISIBLE_DEVICES") + }, + "packages": packages, + "tools": tools, + "accelerator_evidence": { + "rocm_smi": command_output(["rocm-smi", "--showproductname"]), + "nvidia_smi": command_output( + ["nvidia-smi", "--query-gpu=name,memory.total,driver_version", "--format=csv,noheader"] + ), + }, + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--output", type=Path, help="Write JSON to this path instead of stdout") + args = parser.parse_args() + payload = json.dumps(collect(), indent=2, sort_keys=True) + "\n" + if args.output: + args.output.write_text(payload, encoding="utf-8") + else: + print(payload, end="") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/agent-skills/quark-onnx-ptq/SKILL.md b/agent-skills/quark-onnx-ptq/SKILL.md new file mode 100644 index 0000000..b418ed0 --- /dev/null +++ b/agent-skills/quark-onnx-ptq/SKILL.md @@ -0,0 +1,82 @@ +--- +name: quark-onnx-ptq +description: Plan, run, validate, or debug AMD Quark ONNX-to-ONNX post-training quantization. Use for static or dynamic ONNX quantization, INT8/INT4/BF16/BFP/MX formats, calibration data readers, QConfig and ModelQuantizer, Ryzen AI targets, execution-provider issues, AutoSearch, custom operators, or invalid quantized ONNX artifacts. Covers prerequisites through validation without relying on other skills. +--- + +# Quantize ONNX Models with AMD Quark + +Build a reproducible ONNX PTQ workflow around a pinned model, representative data, target execution provider, and measurable acceptance criteria. + +## Workflow + +### 1. Intake and inspect + +Collect: + +- input `.onnx` path, external-data files, model size, opset, and expected input shapes/dtypes; +- deployment target (CPU, CUDA, ROCm, Ryzen AI/NPU, or another runtime) and available execution providers; +- desired format/preset or accuracy, size, and latency goal; +- calibration/evaluation datasets, preprocessing, sample counts, and metric; +- memory, disk, and time budget; output path and overwrite policy. + +Inspect the model before planning: + +```bash +python scripts/inspect_onnx_artifact.py --quantized-model /path/to/input.onnx +python -c "import onnxruntime as ort; print(ort.__version__, ort.get_available_providers())" +``` + +The first command labels an unquantized model as a warning, which is expected during intake. + +### 2. Verify prerequisites + +Use one compatible environment containing Quark, ONNX, and exactly one ONNX Runtime variant. Read [references/workflow-and-presets.md](references/workflow-and-presets.md) and the release-matched [official ONNX guide](https://quark.docs.amd.com/latest/onnx/basic_usage_onnx.html). If custom operators may be needed, explain first-run compilation and confirm before triggering it. + +### 3. Build the plan + +Specify: + +- pinned Quark release and model checksum/path; +- QConfig preset or explicit config, with target-runtime evidence; +- calibration reader behavior, input names/shapes/dtypes, preprocessing, and sample count; +- execution provider and whether CPU fallback is acceptable; +- algorithms, external-data handling, output path, and evaluation threshold; +- estimated peak memory, disk, trial count, and wall time. + +Use basic PTQ first unless there is evidence it misses the accuracy target. AutoSearch is an explicit, budgeted escalation; read [references/autosearch-and-validation.md](references/autosearch-and-validation.md). + +### 4. Confirm and execute + +Show the generated script or exact command. Get explicit confirmation before package changes, downloads, custom-op compilation, quantization, AutoSearch, or overwriting artifacts. Capture stdout/stderr, environment versions, provider list, configuration, and data provenance. + +### 5. Validate + +1. Run structural and ONNX checker validation: + + ```bash + python scripts/inspect_onnx_artifact.py \ + --source-model /path/to/float.onnx \ + --quantized-model /path/to/quantized.onnx \ + --output validation_artifacts.json + ``` + +2. Load the quantized artifact with the intended execution provider and confirm the provider actually used. +3. Run deterministic smoke inference. +4. Compare float and quantized outputs/accuracy using identical preprocessing and data. +5. If performance matters, measure size, peak memory, and latency on the target runtime. + +Report ONNX checker, quantization-marker, metadata/I-O, provider-load, inference, quality, and performance results separately. + +## Rules + +- Do not treat provider availability as provider usage; capture both. +- A calibration reader must emit the model's exact input names, shapes, and dtypes and must yield at least one batch. +- Keep calibration and evaluation data distinct in accuracy claims. +- Preserve every external-data file with the model. Never move only the `.onnx` protobuf. +- Do not infer deployment compatibility from an ONNX checker pass or Q/DQ node count. +- Do not start AutoSearch without a trial/time/disk budget and explicit approval. +- On failure, preserve the first causal error. Later cancellation or provider-fallback messages may be secondary. + +## Deliverable + +Return the pinned inputs, generated config/script, exact command, artifact paths, logs, validation matrix, metric delta, provider evidence, limitations, and smallest recovery step. diff --git a/agent-skills/quark-onnx-ptq/agents/openai.yaml b/agent-skills/quark-onnx-ptq/agents/openai.yaml new file mode 100644 index 0000000..22a7918 --- /dev/null +++ b/agent-skills/quark-onnx-ptq/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Quark ONNX PTQ" + short_description: "Quantize and validate ONNX models with Quark" diff --git a/agent-skills/quark-onnx-ptq/references/autosearch-and-validation.md b/agent-skills/quark-onnx-ptq/references/autosearch-and-validation.md new file mode 100644 index 0000000..3d1cad7 --- /dev/null +++ b/agent-skills/quark-onnx-ptq/references/autosearch-and-validation.md @@ -0,0 +1,42 @@ +# AutoSearch and validation + +## When to use AutoSearch + +Use manual/default PTQ first. Escalate only when the measured quality misses an agreed target and multiple configuration choices are plausible. + +Before launching a search, define: + +- preset or explicit search space; +- calibration reader and optional custom evaluator; +- metric and optimization direction; +- trial count, parallel jobs/devices, wall-time limit, disk estimate, and stop condition; +- persistent output, database, and log paths so a run can be resumed or audited. + +Show the full search script and cost estimate, then get explicit confirmation. The [official AutoSearch Pro guide](https://quark.docs.amd.com/latest/onnx/user_guide_auto_search_pro.html) is the authority for the pinned release's API and presets. + +Do not compare AutoSearch candidates with different data or preprocessing. If using a built-in L1/L2 proxy, say that it is a proxy rather than task accuracy. + +## Validation ladder + +1. **Structure:** files exist; external data resolves; protobuf loads. +2. **Schema:** `onnx.checker.check_model` passes. +3. **Quantization evidence:** expected Q/DQ, quantized operators, initializer types, or Quark domains exist. +4. **Metadata/I-O:** expected model inputs and outputs remain compatible. +5. **Runtime load:** the intended ONNX Runtime/provider loads the model without silent fallback. +6. **Smoke inference:** fixed input produces finite outputs of expected shapes. +7. **Quality:** task metric or numerical comparison meets the approved threshold. +8. **Performance:** target-hardware size, memory, and latency meet the goal. + +The bundled inspector covers only steps 1-4. State that boundary in every result. + +## Recovery clues + +| Symptom | Check first | +|---|---| +| Provider missing | Installed `onnxruntime*` variant and `ort.get_available_providers()` | +| Silent CPU fallback | Providers passed to the actual session, not only available providers | +| No calibration data | Reader input names, first batch, rewind, and sample count | +| Model exceeds 2 GB | External-data export and colocated data files | +| Custom op not registered | Quark custom-op build, ABI-compatible ORT, and session registration | +| Accuracy collapse | Float baseline, preprocessing parity, calibration representativeness, excluded nodes, preset/algorithm | +| OOM or disk exhaustion | Batch/sample count, disk cache, worker count, external data, AutoSearch trials | diff --git a/agent-skills/quark-onnx-ptq/references/workflow-and-presets.md b/agent-skills/quark-onnx-ptq/references/workflow-and-presets.md new file mode 100644 index 0000000..9a95315 --- /dev/null +++ b/agent-skills/quark-onnx-ptq/references/workflow-and-presets.md @@ -0,0 +1,54 @@ +# ONNX workflow and preset selection + +Pin the Quark release and verify the API against that release. The basic flow is model inspection, calibration reader construction, QConfig selection, `ModelQuantizer`, and validation. + +## Environment checks + +```bash +python -m pip show amd-quark onnx onnxruntime onnxruntime-gpu onnxruntime-rocm +python -c "import quark, onnx, onnxruntime as ort; print(quark.__version__, onnx.__version__, ort.__version__); print(ort.get_available_providers())" +``` + +Quark 0.12 documentation requires ONNX Runtime `>=1.22.2,<=1.25.1`. Install only one runtime variant. Current ROCm 7.x guidance may use CPU ONNX Runtime; do not assume a ROCm execution provider exists. + +## Minimal static PTQ shape + +Adapt this from the [official basic usage guide](https://quark.docs.amd.com/latest/onnx/basic_usage_onnx.html). The data reader is model-specific; the placeholder must not be executed unchanged. + +```python +from quark.onnx import ModelQuantizer, QConfig + +input_model_path = "/path/to/float.onnx" +quantized_model_path = "/path/to/quantized.onnx" + +# Implement an onnxruntime CalibrationDataReader that emits dictionaries +# keyed by the model's actual input names and rewinds deterministically. +calibration_data_reader = build_calibration_reader(input_model_path, calibration_inputs) + +# Example only. Select a preset supported by the pinned release and target. +quantization_config = QConfig.get_default_config("A8W8") +quantizer = ModelQuantizer(quantization_config) +quantizer.quantize_model(input_model_path, quantized_model_path, calibration_data_reader) +``` + +Before execution, print the full generated script and its data paths. + +## Preset decision rules + +- Use a documented default preset that matches the deployment target before composing a custom config. +- Confirm whether the target accepts QDQ, QOperator, custom `com.amd.quark` operators, wide integer types, BFP, or MX formats. +- INT8 activation/weight PTQ generally requires representative calibration data. +- Weight-only INT4 and dynamic quantization have different calibration and runtime requirements; verify both API and consumer support. +- Ryzen AI/NPU paths may require power-of-two scales, layout conversion, and a target-specific configuration. Follow the current supported-accelerator guide. +- Large models may require ONNX external data. Keep the `.onnx` file and all referenced data together. + +## Calibration-reader contract + +Validate at least one batch before quantization: + +1. keys equal model input names; +2. arrays match expected dtype and rank; +3. dynamic dimensions are resolved consistently; +4. preprocessing matches evaluation and deployment; +5. `rewind()` resets iteration; +6. the reader yields the planned sample count and never silently yields zero. diff --git a/agent-skills/quark-onnx-ptq/scripts/inspect_onnx_artifact.py b/agent-skills/quark-onnx-ptq/scripts/inspect_onnx_artifact.py new file mode 100755 index 0000000..b315cfb --- /dev/null +++ b/agent-skills/quark-onnx-ptq/scripts/inspect_onnx_artifact.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved. +# SPDX-License-Identifier: MIT + +"""Inspect an ONNX artifact and compare its public I/O with a float source model.""" + +from __future__ import annotations + +import argparse +import json +from collections import Counter +from pathlib import Path + + +def value_info_signature(items) -> list[dict[str, object]]: + result = [] + for item in items: + tensor = item.type.tensor_type + dims: list[int | str | None] = [] + for dim in tensor.shape.dim: + dims.append(dim.dim_value or dim.dim_param or None) + result.append({"name": item.name, "elem_type": tensor.elem_type, "shape": dims}) + return result + + +def model_summary(path: Path) -> tuple[dict[str, object] | None, list[str]]: + errors: list[str] = [] + try: + import onnx + except ImportError as error: + return None, [f"onnx is not installed: {error}"] + try: + model = onnx.load(str(path), load_external_data=False) + onnx.checker.check_model(model, full_check=False) + except Exception as error: # ONNX exposes several checker/load exception types. + return None, [f"cannot load/check {path}: {error}"] + + op_counts = Counter(node.op_type for node in model.graph.node) + domains = Counter((node.domain or "ai.onnx") for node in model.graph.node) + quant_ops = { + name: count + for name, count in op_counts.items() + if name in {"QuantizeLinear", "DequantizeLinear", "DynamicQuantizeLinear", "MatMulNBits"} or "Quant" in name + } + external_files = sorted( + { + entry.value + for initializer in model.graph.initializer + for entry in initializer.external_data + if entry.key == "location" + } + ) + missing_external = [name for name in external_files if not (path.parent / name).is_file()] + if missing_external: + errors.append("external data files are missing") + return { + "path": str(path.resolve()), + "size_bytes": path.stat().st_size, + "ir_version": model.ir_version, + "opsets": {item.domain or "ai.onnx": item.version for item in model.opset_import}, + "inputs": value_info_signature(model.graph.input), + "outputs": value_info_signature(model.graph.output), + "operator_counts": dict(sorted(op_counts.items())), + "domain_counts": dict(sorted(domains.items())), + "quantization_operator_counts": dict(sorted(quant_ops.items())), + "quark_domain_nodes": sum(count for domain, count in domains.items() if domain.startswith("com.amd.quark")), + "external_data_files": external_files, + "missing_external_data_files": missing_external, + }, errors + + +def inspect(source: Path | None, quantized: Path) -> dict[str, object]: + quant_summary, errors = model_summary(quantized) + if quant_summary is None: + return {"status": "failed", "errors": errors} + warnings: list[str] = [] + marker_count = sum(quant_summary["quantization_operator_counts"].values()) + quant_summary["quark_domain_nodes"] + if marker_count == 0: + warnings.append("no quantization operator or com.amd.quark node was detected") + + comparison = None + if source: + source_summary, source_errors = model_summary(source) + errors.extend(source_errors) + if source_summary: + comparison = { + "inputs_equal": source_summary["inputs"] == quant_summary["inputs"], + "outputs_equal": source_summary["outputs"] == quant_summary["outputs"], + "source_size_bytes": source_summary["size_bytes"], + "quantized_size_bytes": quant_summary["size_bytes"], + } + if not comparison["inputs_equal"] or not comparison["outputs_equal"]: + warnings.append("public input/output signatures differ from the source model") + + return { + "status": "failed" if errors else "ok", + "quantized": quant_summary, + "comparison": comparison, + "warnings": warnings, + "errors": errors, + "scope": "structure and ONNX schema only; provider load, inference, accuracy, and performance remain required", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-model", type=Path) + parser.add_argument("--quantized-model", type=Path, required=True) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + result = inspect(args.source_model, args.quantized_model) + payload = json.dumps(result, indent=2, sort_keys=True) + "\n" + if args.output: + args.output.write_text(payload, encoding="utf-8") + else: + print(payload, end="") + return 1 if result["status"] == "failed" else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/agent-skills/quark-torch-ptq/SKILL.md b/agent-skills/quark-torch-ptq/SKILL.md new file mode 100644 index 0000000..959a4f3 --- /dev/null +++ b/agent-skills/quark-torch-ptq/SKILL.md @@ -0,0 +1,80 @@ +--- +name: quark-torch-ptq +description: Plan, run, validate, or debug AMD Quark post-training quantization for PyTorch and Hugging Face models. Use for LLM FP8, INT8, INT4, MX, AWQ, GPTQ, SmoothQuant, KV-cache quantization, Quark export, quantized-model evaluation, or a failed quark.torch workflow. Covers prerequisites through deployable artifacts without relying on other skills. +--- + +# Quantize PyTorch Models with AMD Quark + +Turn a PyTorch or Hugging Face model and deployment goal into an evidence-backed Quark PTQ run. Do not treat artifact creation as proof of accuracy or runtime compatibility. + +## Workflow + +### 1. Intake + +Collect: + +- model ID or local path, exact revision, architecture, parameter count, and access requirements; +- target runtime/hardware and required export format; +- precision/scheme request, or the user's latency, memory, and accuracy goal; +- calibration dataset, sample count, sequence length, and redistribution rights; +- available host RAM, GPU count/memory, disk, and time budget; +- float baseline metric and acceptable regression. + +Inspect local files and hardware rather than assuming them. If installation is incomplete, use the install and verification procedure in [references/prerequisites.md](references/prerequisites.md). + +### 2. Choose a release-matched recipe + +Read [references/recipe-selection.md](references/recipe-selection.md). Prefer a Quark example that explicitly supports the model family and export target. Pin a Quark release or commit and use the example shipped with that same release; do not mix `latest` examples with an older installed package. + +Produce a plan with model/revision, scheme, algorithms, calibration input, device placement, output/export format, evaluation, expected disk/memory cost, and exact command. Mark every unverified assumption. + +### 3. Confirm before expensive or mutating work + +Get explicit confirmation before package changes, gated model downloads, calibration-data downloads, generated scripts, quantization, export, or evaluation. Show output paths and whether an existing path could be overwritten. + +### 4. Preflight and execute + +1. Verify Quark/PyTorch imports and backend alignment. +2. Verify model and calibration access before allocating accelerators. +3. In a Quark examples checkout or release bundle, inspect the command surface: + + ```bash + python quantize_quark.py --help + ``` + +4. Run the approved command with a persistent log and capture package versions, revision, arguments, and accelerator visibility. +5. Preserve the float model; write quantized output to a distinct directory. + +### 5. Validate in layers + +Read [references/artifacts-and-evaluation.md](references/artifacts-and-evaluation.md), then: + +1. Inspect exported artifacts: + + ```bash + python scripts/inspect_artifacts.py \ + --source-model-dir /path/to/float-model \ + --quantized-model-dir /path/to/output \ + --output validation_artifacts.json + ``` + +2. Load the artifact with the intended consumer, not only Quark. +3. Run one deterministic smoke inference. +4. Compare the agreed accuracy metric against the float baseline on the same data and preprocessing. +5. Measure memory/latency in the target runtime if performance motivated quantization. + +Report structural, load, numerical, quality, and runtime results separately. A missing test is `not-run`, never a pass. + +## Rules + +- Do not invent a quantization scheme from model size alone. Tie the choice to supported model/format/runtime evidence. +- Keep calibration and evaluation data distinct when reporting accuracy. +- Record tokenizer revision and `trust_remote_code` decisions. +- For multi-GPU runs, verify every checkpoint shard is readable before GPU allocation. Use explicit placement and capture it. +- Never overwrite the source model. Treat large outputs and model downloads as cost-bearing actions. +- If a process dies and only a cancellation error remains, inspect earlier logs for the first GPU/OOM/compiler failure. +- Never call an output deployable until its target loader and one smoke inference pass. + +## Deliverable + +Return a concise run record containing the pinned inputs, exact command, output location, logs, validation matrix, metric comparison, limitations, and smallest recovery step for any failure. diff --git a/agent-skills/quark-torch-ptq/agents/openai.yaml b/agent-skills/quark-torch-ptq/agents/openai.yaml new file mode 100644 index 0000000..09f3699 --- /dev/null +++ b/agent-skills/quark-torch-ptq/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Quark PyTorch PTQ" + short_description: "Quantize and validate PyTorch models with Quark" diff --git a/agent-skills/quark-torch-ptq/references/artifacts-and-evaluation.md b/agent-skills/quark-torch-ptq/references/artifacts-and-evaluation.md new file mode 100644 index 0000000..dd92f0a --- /dev/null +++ b/agent-skills/quark-torch-ptq/references/artifacts-and-evaluation.md @@ -0,0 +1,37 @@ +# Artifact and evaluation checks + +## Structural checks + +- Output path differs from the float source. +- `config.json`, tokenizer assets, and weight shards/indexes expected by the export format exist. +- Every shard referenced by an index is present and readable. +- Quantization metadata is present and consistent with the requested scheme. +- Non-weight auxiliary files required by the loader were preserved. + +Run `scripts/inspect_artifacts.py` for a dependency-free first pass. Its `ok` status means the directory is structurally plausible, not numerically correct. + +## Consumer load and smoke inference + +Use the exact runtime that will consume the artifact: Transformers, vLLM, SGLang, llama.cpp/GGUF, or another target. Record its version and loading arguments. Run a fixed short prompt/input and retain the output and error log. + +## Quality evaluation + +Compare float and quantized models using: + +- identical model/tokenizer revision; +- identical dataset split, preprocessing, sequence length, and random seed; +- the same metric implementation; +- enough samples to make the stated conclusion credible. + +Report baseline, quantized value, absolute/relative delta, sample count, and whether the result meets the user-approved threshold. Perplexity, lm-eval tasks, ROUGE/METEOR, or domain metrics may be appropriate; choose based on the deployment goal. + +## Runtime evaluation + +If the goal is performance, measure on target hardware after warmup: + +- peak GPU and host memory; +- prefill and decode throughput/latency as applicable; +- model load time and output size; +- runtime flags such as tensor parallelism, batch size, and sequence length. + +Do not compare measurements collected with different runtime settings without calling out the difference. diff --git a/agent-skills/quark-torch-ptq/references/prerequisites.md b/agent-skills/quark-torch-ptq/references/prerequisites.md new file mode 100644 index 0000000..a04a896 --- /dev/null +++ b/agent-skills/quark-torch-ptq/references/prerequisites.md @@ -0,0 +1,28 @@ +# PyTorch PTQ prerequisites + +Use one Python environment with a supported AMD Quark release and a PyTorch build matching the accelerator. + +## Inspect + +```bash +python --version +python -m pip show amd-quark torch transformers +python -c "import quark, torch; print(quark.__version__, torch.__version__); print(torch.version.cuda, torch.version.hip); print(torch.cuda.is_available(), torch.cuda.device_count())" +``` + +For GPU use, a false `torch.cuda.is_available()` or a backend mismatch is a blocker. AMD ROCm builds still expose devices through PyTorch's `torch.cuda` API; confirm `torch.version.hip` for ROCm. + +## Install planning + +Use the release-matched [Quark installation guide](https://quark.docs.amd.com/latest/install.html). Confirm before changing packages. The default Quark distribution is `amd-quark`; GPU PyTorch must come from the correct backend index. + +The LLM PTQ example may require additional packages such as Transformers, Accelerate, Datasets, Evaluate, GGUF, and lm-eval. Use the `requirements.txt` shipped beside the release-matched example instead of copying an unpinned package list from another release. + +## Source of runnable examples + +Use one of: + +- `examples/torch/language_modeling/llm_ptq` in a Quark checkout pinned to the installed version; +- the examples bundle distributed with the same Quark release. + +Record `git rev-parse HEAD` or the release archive version in the run manifest. diff --git a/agent-skills/quark-torch-ptq/references/recipe-selection.md b/agent-skills/quark-torch-ptq/references/recipe-selection.md new file mode 100644 index 0000000..1b756d7 --- /dev/null +++ b/agent-skills/quark-torch-ptq/references/recipe-selection.md @@ -0,0 +1,52 @@ +# PyTorch recipe selection + +Start from the release-matched [LLM PTQ guide](https://quark.docs.amd.com/latest/pytorch/example_quark_torch_llm_ptq.html) and its supported-model table. + +## Selection guide + +| Goal | Candidate | What must be verified | +|---|---|---| +| Broad accelerator-friendly LLM compression | FP8 | Model-family support, FP8-capable target runtime, KV-cache format if requested | +| CPU/static integer path | INT8 | Calibration representativeness and target operator support | +| Maximum weight-memory reduction | INT4/UINT4 weight-only | Group size, AWQ/GPTQ support, target loader compatibility | +| OCP microscaling experiment | MXFP4/MXFP6 | Model and export support plus consuming runtime support | +| Recover accuracy after basic PTQ | AWQ, GPTQ, SmoothQuant, rotation | Algorithm support, calibration cost, and export restrictions | + +Do not infer support from a precision name alone. Check the current guide and `quantize_quark.py --help` for the pinned release. + +## Release-0.12-era command shapes + +These examples show the planning shape; validate every flag against the pinned script before execution. + +```bash +# FP8 plus FP8 KV cache, Hugging Face export +python quantize_quark.py \ + --model_dir /path/to/model \ + --output_dir /path/to/output \ + --quant_scheme fp8 \ + --kv_cache_dtype fp8 \ + --num_calib_data 128 \ + --model_export hf_format + +# INT4 weight-only with AWQ +python quantize_quark.py \ + --model_dir /path/to/model \ + --output_dir /path/to/output \ + --quant_scheme int4_wo_128 \ + --num_calib_data 128 \ + --quant_algo awq \ + --dataset pileval_for_awq_benchmark \ + --seq_len 512 \ + --model_export hf_format + +# INT8 on CPU +python quantize_quark.py \ + --model_dir /path/to/model \ + --output_dir /path/to/output \ + --quant_scheme int8 \ + --num_calib_data 128 \ + --device cpu \ + --model_export hf_format +``` + +For multi-GPU placement, inspect the pinned release's `--multi_gpu` choices and check free memory on every visible device. Very large MoE models also require sufficient host virtual-memory mappings and disk space; capture those checks in the plan. diff --git a/agent-skills/quark-torch-ptq/scripts/inspect_artifacts.py b/agent-skills/quark-torch-ptq/scripts/inspect_artifacts.py new file mode 100755 index 0000000..6e47e3b --- /dev/null +++ b/agent-skills/quark-torch-ptq/scripts/inspect_artifacts.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026, Advanced Micro Devices, Inc. All rights reserved. +# SPDX-License-Identifier: MIT + +"""Perform a dependency-free structural inspection of a Quark Torch export.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def load_json(path: Path) -> tuple[dict | None, str | None]: + if not path.is_file(): + return None, f"missing {path.name}" + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + return None, f"cannot read {path.name}: {error}" + return value if isinstance(value, dict) else None, None if isinstance( + value, dict + ) else f"{path.name} is not an object" + + +def quantization_keys(value: object, prefix: str = "") -> list[str]: + found: list[str] = [] + if isinstance(value, dict): + for key, child in value.items(): + path = f"{prefix}.{key}" if prefix else str(key) + if "quant" in str(key).lower() or str(key).lower() in {"bits", "dtype", "scheme"}: + found.append(path) + found.extend(quantization_keys(child, path)) + elif isinstance(value, list): + for index, child in enumerate(value[:100]): + found.extend(quantization_keys(child, f"{prefix}[{index}]")) + return found + + +def inspect(source: Path | None, output: Path) -> dict[str, object]: + errors: list[str] = [] + warnings: list[str] = [] + if not output.is_dir(): + return {"status": "failed", "errors": [f"not a directory: {output}"]} + + config, config_error = load_json(output / "config.json") + if config_error: + errors.append(config_error) + weight_files = sorted(path.name for path in output.glob("*.safetensors")) + weight_files += sorted(path.name for path in output.glob("*.bin")) + indexes = sorted(path.name for path in output.glob("*.index.json")) + if not weight_files: + errors.append("no .safetensors or .bin weight files found") + markers = sorted(set(quantization_keys(config or {}))) + if not markers: + warnings.append("no quantization-related key found in config.json") + + missing_index_shards: list[str] = [] + for index_name in indexes: + index, error = load_json(output / index_name) + if error or not index: + warnings.append(error or f"empty {index_name}") + continue + for shard in set((index.get("weight_map") or {}).values()): + if isinstance(shard, str) and not (output / shard).is_file(): + missing_index_shards.append(shard) + if missing_index_shards: + errors.append("index references missing shards") + + source_aux_missing: list[str] = [] + if source and source.is_dir(): + for candidate in ("tokenizer.json", "tokenizer_config.json", "generation_config.json"): + if (source / candidate).is_file() and not (output / candidate).is_file(): + source_aux_missing.append(candidate) + if source_aux_missing: + warnings.append("some source auxiliary files are absent from output") + + return { + "status": "failed" if errors else "ok", + "quantized_model_dir": str(output.resolve()), + "config_present": config is not None, + "quantization_markers": markers, + "weight_files": weight_files, + "weight_bytes": sum((output / name).stat().st_size for name in weight_files), + "index_files": indexes, + "missing_index_shards": sorted(set(missing_index_shards)), + "source_auxiliary_missing": source_aux_missing, + "warnings": warnings, + "errors": errors, + "scope": "structural inspection only; consumer load, inference, and accuracy remain required", + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-model-dir", type=Path) + parser.add_argument("--quantized-model-dir", type=Path, required=True) + parser.add_argument("--output", type=Path) + args = parser.parse_args() + result = inspect(args.source_model_dir, args.quantized_model_dir) + payload = json.dumps(result, indent=2, sort_keys=True) + "\n" + if args.output: + args.output.write_text(payload, encoding="utf-8") + else: + print(payload, end="") + return 1 if result["status"] == "failed" else 0 + + +if __name__ == "__main__": + raise SystemExit(main())