From 0fc5bc45f8c68d05f7ef37902830830fca245027 Mon Sep 17 00:00:00 2001 From: Vlad Stirbu Date: Wed, 19 Nov 2025 16:40:43 +0200 Subject: [PATCH] Add initial project files and configurations - Create .env.q8s for MLflow tracking URI - Update .gitignore to include kubeconfig.yaml - Modify Q8Sproject to reference kubeconfig.yaml - Enhance README.md with cluster configuration and execution instructions - Add sample workload scripts for Qiskit and MLflow integration - Create pyproject.toml for package management - Update requirements-dev.txt for q8s version --- .env.q8s | 1 + .gitignore | 4 +- Q8Sproject | 2 +- README.md | 36 +++++ notebook.ipynb | 142 +++++++++++++++++++ pyproject.toml | 14 ++ requirements-dev.txt | 2 +- src/workloads/sample_workload.py | 28 ++++ src/workloads/sample_workload_with_mlflow.py | 49 +++++++ 9 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 .env.q8s create mode 100644 notebook.ipynb create mode 100644 pyproject.toml create mode 100644 src/workloads/sample_workload.py create mode 100644 src/workloads/sample_workload_with_mlflow.py diff --git a/.env.q8s b/.env.q8s new file mode 100644 index 0000000..165265b --- /dev/null +++ b/.env.q8s @@ -0,0 +1 @@ +export MLFLOW_TRACKING_URI="http://mlflow-service:5000" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7f8e343..7863bd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.ipynb_checkpoints .q8s_cache -.venv \ No newline at end of file +.venv +kubeconfig.yaml \ No newline at end of file diff --git a/Q8Sproject b/Q8Sproject index 9f053d2..be45019 100644 --- a/Q8Sproject +++ b/Q8Sproject @@ -20,4 +20,4 @@ docker: username: qubernetes-dev registry: ghcr.io -kubeconfig: config.vlad.yaml +kubeconfig: kubeconfig.yaml diff --git a/README.md b/README.md index f2e0ff3..72cfee1 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,39 @@ Initialize the Q8S project: ```bash q8sctl init --images ``` + +### Getting the cluster configurations + +Login into the Qubernetes [Console](https://console.em4qs.qubernetes.dev) select the Settings -> General option and download the kubeconfig file for your cluster. Save it as `kubeconfig.yaml` in the project root directory. + +### Getting a PAT for GitHub Container Registry + +To execute workloads that have images in GitHub Container Registry (GHCR), you need a Personal Access Token (PAT) with the `read:packages` scope. Follow [GitHub’s guide on creating a Personal Access Token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) to create one. + +## Execute workloads from command line + +To execute workloads in your Qubernetes cluster, use the following command: + +```bash +q8sctl execute --registry_pat YOUR_GHCR_PAT --target cpu /src/workloads/sample_workload.py +``` + +## Execute workloads from Jupyter Lab notebooks + +Install Jupyter Lab if you haven't already: + +```bash +pip install jupyter +``` + +To execute workloads from notebooks, start Jupyter Lab with the following command: + +```bash +q8sctl jupyter --registry_pat YOUR_GHCR_PAT --target cpu --install +``` + +Open the `notebook.ipynb` file in Jupyter Lab. Select the kernel `Q8s kernel`. + +## Building Images with CI + +The image build workflow is defined in `.github/workflows/build-images.yaml`. It is triggered when a new branch is created or when a commit with a modified `Q8Sproject` file is pushed. diff --git a/notebook.ipynb b/notebook.ipynb new file mode 100644 index 0000000..cc7a965 --- /dev/null +++ b/notebook.ipynb @@ -0,0 +1,142 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "id": "d08f086a-a30a-4be7-8edd-6eb2e72940e5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " ┌───┐ ┌─┐ " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "q_0: ┤ H ├──■──┤M├───" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " └───┘┌─┴─┐└╥┘┌─┐" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "q_1: ─────┤ X ├─╫─┤M├" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " └───┘ ║ └╥┘" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "c: 2/═══════════╩══╩═" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + " 0 1 " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "{'00': 497, '11': 503}" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import logging\n", + "\n", + "from qiskit import QuantumCircuit, transpile\n", + "from qiskit_aer import AerSimulator\n", + "\n", + "logger = logging.getLogger(__name__)\n", + "\n", + "\n", + "def demo_function(shotsAmount=1000, device=\"CPU\"):\n", + " simulator = AerSimulator(method=\"statevector\", device=device)\n", + "\n", + " circuit = QuantumCircuit(2, 2)\n", + " circuit.h(0)\n", + " circuit.cx(0, 1)\n", + " circuit.measure([0, 1], [0, 1])\n", + "\n", + " compiled_circuit = transpile(circuit, simulator)\n", + " job = simulator.run(compiled_circuit, shots=shotsAmount)\n", + " result = job.result()\n", + " counts = result.get_counts()\n", + " print(circuit)\n", + " return counts\n", + "\n", + "counts = demo_function()\n", + "print(counts)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c25eba4c-983b-4932-baf1-fa4f67d346e9", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Q8s kernel", + "language": "python", + "name": "q8s" + }, + "language_info": { + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "Any text", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2d93596 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "workloads" +version = "0.1.0" +description = "A minimal Python package" +readme = "README.md" +requires-python = ">=3.10" + +# Optional but recommended when using src layout +[tool.setuptools.packages.find] +where = ["src"] diff --git a/requirements-dev.txt b/requirements-dev.txt index 54a424b..b022239 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1 @@ -q8s>=0.8.0 +q8s>=0.9.0 diff --git a/src/workloads/sample_workload.py b/src/workloads/sample_workload.py new file mode 100644 index 0000000..c74e1e3 --- /dev/null +++ b/src/workloads/sample_workload.py @@ -0,0 +1,28 @@ +import logging + +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator + +logger = logging.getLogger(__name__) + + +def demo_function(shotsAmount=1000, device="GPU"): + simulator = AerSimulator(method="statevector", device=device) + + circuit = QuantumCircuit(2, 2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure([0, 1], [0, 1]) + + compiled_circuit = transpile(circuit, simulator) + job = simulator.run(compiled_circuit, shots=shotsAmount) + result = job.result() + counts = result.get_counts() + logger.debug(circuit) + return counts + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + output = demo_function() + print("Simulation result:", output) diff --git a/src/workloads/sample_workload_with_mlflow.py b/src/workloads/sample_workload_with_mlflow.py new file mode 100644 index 0000000..53c940c --- /dev/null +++ b/src/workloads/sample_workload_with_mlflow.py @@ -0,0 +1,49 @@ +import logging +import os +import mlflow + +from qiskit import QuantumCircuit, transpile +from qiskit_aer import AerSimulator + +logger = logging.getLogger(__name__) + + +def demo_function(shotsAmount=1000, device="CPU"): + simulator = AerSimulator(method="statevector", device=device) + + circuit = QuantumCircuit(2, 2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure([0, 1], [0, 1]) + + compiled_circuit = transpile(circuit, simulator) + job = simulator.run(compiled_circuit, shots=shotsAmount) + result = job.result() + counts = result.get_counts() + logger.debug(circuit) + return counts + + +if __name__ == "__main__": + mlflow.set_experiment("Sample Workload Experiment") + + with mlflow.start_run(): + # Set Git-related tags for traceability + mlflow.set_tag( + "mlflow.source.git.commit", os.getenv("MLFLOW_GIT_COMMIT", "unknown") + ) + mlflow.set_tag( + "mlflow.source.git.branch", os.getenv("MLFLOW_GIT_BRANCH", "unknown") + ) + mlflow.set_tag( + "mlflow.source.git.repoURL", os.getenv("MLFLOW_GIT_REPO_URL", "unknown") + ) + + # logging.basicConfig(level=logging.DEBUG) + + shots = 1000 + device = "GPU" + mlflow.log_param("shots", shots) + mlflow.log_param("device", device) + counts = demo_function(shotsAmount=shots, device=device) + mlflow.log_dict(counts, "simulation_counts.json")