Attention
This post was originally published on Red Hat Developer, the community to learn, code, and share faster. To read the original post, click here.
Check the video version for Red Hat TV
In this article we cover the required steps to configure Kernel Module Management Operator (KMM) and use it to deploy an out-of-tree (OOT) kernel module, as well as leveraging other related technologies to build a toolset for hardware enablement. To illustrate that process, we’ll leverage the Intel Data Center GPU Flex 140.
What is the Kernel Module Management Operator?
The Kernel Module Management Operator manages, builds, signs, and deploys out-of-tree (OOT) kernel modules and device plug-ins on Red Hat OpenShift Container Platform clusters.
Before KMM, cluster admins had to manually install drivers to multiple nodes. Upgrades were painful and prone to errors from incompatible drivers. Furthermore, workloads might get scheduled to a node with broken drivers causing scheduling issues or missing hardware. KMM solves all of these problems, as we’ll see.
KMM is designed to accommodate multiple kernel versions at once for any kernel module, allowing for seamless node upgrades and reduced application downtime. For more information, refer to the Kernel Module Management Operator product documentation.
KMM is also a community project, which you can test on upstream Kubernetes, and there is a Slack community channel.
Prerequisites
For this scenario, we’ll require an already working OpenShift environment as we will use it to deploy the different tools on top. Check the documentation for instructions.
KMM will require a registry to push images to. If you’ve installed on bare metal, ensure the internal registry is enabled and configured (refer to Installing a user-provisioned cluster on bare metal).
Additionally, this tutorial references data available from Intel at the following locations:
Set up Node Feature Discovery Operator
Node Feature Discovery (NFD) detects hardware features available on nodes and advertises those features using nodes labels, so that they can later be used as selector for scheduling decisions.
The NFD Operator automatically adds labels to the nodes that present some characteristics, including if the node has a GPU and which GPU it has.
It’s an ideal way to identify which nodes require a kernel module to be enabled for the specific node(s) and later use it to instruct KMM to build it only for those.
We can install it via the following YAML:
---
apiVersion: v1
kind: Namespace
metadata:
name: openshift-nfd
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: openshift-nfd
namespace: openshift-nfd
spec:
targetNamespaces:
- openshift-nfd
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: nfd
namespace: openshift-nfd
spec:
channel: "stable"
installPlanApproval: Automatic
name: nfd
source: redhat-operators
sourceNamespace: openshift-marketplace
Once installed, we can create a CRD as described in the NFD Operator documentation to start.
Create a file named nfdcr.yaml
with the following contents:
apiVersion: nfd.openshift.io/v1
kind: NodeFeatureDiscovery
metadata:
name: nfd-instance
namespace: openshift-nfd
spec:
operand:
image: quay.io/openshift/origin-node-feature-discovery:4.14
imagePullPolicy: Always
servicePort: 12000
workerConfig:
configData: |
Then apply it with:
oc apply -f nfdcr.yaml
Additionally, create a nodefeaturerule.yaml:
apiVersion: nfd.openshift.io/v1alpha1
kind: NodeFeatureRule
metadata:
name: intel-dp-devices
spec:
rules:
- name: "intel.gpu"
labels:
"intel.feature.node.kubernetes.io/gpu": "true"
matchFeatures:
- feature: pci.device
matchExpressions:
vendor: { op: In, value: ["8086"] }
class: { op: In, value: ["0300", "0380"] }
And once it has been applied, oc describe node
displays the labels applied. Below is a partial output of the labels that NFD applies:
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
feature.node.kubernetes.io/cpu-cpuid.ADX=true
feature.node.kubernetes.io/cpu-cpuid.AESNI=true
feature.node.kubernetes.io/cpu-cpuid.AMXINT8=true
feature.node.kubernetes.io/cpu-cpuid.AMXTILE=true
feature.node.kubernetes.io/cpu-cpuid.AVX=true
feature.node.kubernetes.io/cpu-cpuid.AVX2=true
…
As we can see, it starts showing some of the CPU flags among other values as labels in our system. We can then grep for Intel-specific GPU labels with:
$ oc describe node rhvp-intel-01|grep intel.feature
intel.feature.node.kubernetes.io/gpu=true
…where we can see that NFD has detected the GPU.
At this point, NFD will take care of our node’s HW detection and labeling, which can later be used as node selectors for KMM to deploy the required modules.
Advanced node labeling
For this use case, the Intel Data Center GPU Driver for OpenShift (i915) driver is only available and tested for some kernel versions.
Using NFD labels, we can target specific custom kernel versions for our module deployment and enablement so that only hosts with the required kernel and the required hardware are enabled for driver activation. This ensures that only compatible drivers are installed on nodes with a supported kernel, which is what makes KMM so valuable.
In this case, we’ll be using nodes that contain the intel.feature.node.kubernetes.io/gpu
tag.
Let’s move on to the next steps in our journey, installing KMM and building the kernel module with Driver Toolkit (DTK). DTK is a container image used as a base image where drivers can be built, as it includes kernel packages, some tools, etc.
Set up Kernel Module Management Operator
Install KMM using OperatorHub in our OpenShift Console or the following kmm.yaml
:
---
apiVersion: v1
kind: Namespace
metadata:
name: openshift-kmm
---
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
name: kernel-module-management
namespace: openshift-kmm
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: kernel-module-management
namespace: openshift-kmm
spec:
channel: "stable"
installPlanApproval: Automatic
name: kernel-module-management
source: redhat-operators
sourceNamespace: openshift-marketplace
With:
oc apply -f kmm.yaml
Once it’s done, we can switch to the openshift-kmm project:
oc project openshift-kmm
Create a kernel module
As mentioned previously, KMM can perform the compilation and installation of kernel module drivers for our hardware.
A kernel module can:
- Have dependencies
- Replace an existing driver
Let’s explore the use cases in the next sections of this article.
Integration with NFD via CRDs
KMM uses a kmod (kernel module) image to define which kernel modules to load, which is an OCI image that contains the .ko
files.
In .spec.selector
, we can define which nodes should be selected and as we showcased earlier, we can target one specific label added by NFD, so that only those nodes are targeted for loading the module for the hardware installed.
KMM dependencies
Adding a module might have additional module dependencies, that is, extra modules that need to be already loaded in the kernel.
We can use the Custom Resource Definition (CRD) field .spec.moduleLoader.container.modprobe.modulesLoadingOrder
to identify the order for module loading starting with upmost module, then the module it depends on, and so on.
Replace an in-tree module with an out-of-tree module
Similar to dependencies, sometimes the module being loaded conflicts with an already loaded kernel module.
In this case, we need to have KMM first remove the conflicting module via the .spec.moduleLoader.container.inTreeModuleToRemove
field of the CRD. KMM will then proceed and load the newer OOT module.
For the Intel Data Center GPU Flex series, the intel_vsec
and i915
drivers will have to be removed which will be discussed later in this article.
Configure the Driver Toolkit for image building
The Driver Toolkit) provides a base image with required kernel development packages that are used to build specific drivers for our platform, which match the kernel version used on each node where the accelerators exist.
By using a specially crafted Containerfile containing a reference to DTK_AUTO
as shown below:
ARG DTK_AUTO
FROM ${DTK_AUTO}
RUN gcc \... |
The KMM Operator will replace the required variables as well as pull and build the image for the driver.
And that’s all. Easy, right?
Manage heterogeneous nodes in the cluster
As we’re using labels for selecting specific nodes in our cluster, we can keep a mix of nodes with or without the hardware in our cluster. KMM will take care of loading the required modules on the matching nodes, leaving the other ones without the specific accelerator.
In our case we’re using intel.feature.node.kubernetes.io/gpu=true
as the label to match our intended nodes, leaving other nodes without the GPU affected.
Enable the Intel Data Center GPU Flex 140
We’re going to explore the step-by-step the process for detecting, configuring, and enabling an Intel GPU in our OpenShift environment.
One of our workers has the accelerator installed, as reported by lspci | egrep 'Graphic|Display
:
02:00.0 VGA compatible controller: ASPEED Technology, Inc. ASPEED Graphics Family (rev 52)
a0:00.0 Display controller: Intel Corporation Data Center GPU Flex 140 (rev 05)
Let’s create a MachineConfigPool
(MCP) to apply the configuration in our environment:
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfigPool
metadata:
name: intel-dgpu
spec:
machineConfigSelector:
matchExpressions:
- {
key: machineconfiguration.openshift.io/role,
operator: In,
values: [worker, intel-dgpu, master],
}
nodeSelector:
matchLabels:
intel.feature.node.kubernetes.io/gpu: "true"
Note
If you’re using single node OpenShift for this test, remember that the YAMLs must be adapted so that the configuration via MCO applies to the primary MCP; that is, using the selector machineconfiguration.openshift.io/role: master
.
Using a machine configuration, we can define new parameters, like for example disable the built-in drivers with this YAML:
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: intel-dgpu
name: 100-intel-dgpu-machine-config-disable-i915
spec:
config:
ignition:
version: 3.2.0
storage:
files:
- contents:
source: data:,blacklist%20i915
mode: 0644
overwrite: true
path: /etc/modprobe.d/blacklist-i915.conf
- contents:
source: data:,blacklist%20intel_vsec
mode: 0644
overwrite: true
path: /etc/modprobe.d/blacklist-intel-vsec.conf
After this YAML is applied, we can check that there are no modules applied via oc debug node/rhvp-intel-01
(NOTE: if you see intel_vsec
or i915
in the output, verify that the MachineConfig
defined was correctly applied):
$ lsmod|egrep 'i915|vsec'
Now, we need to define the path to find the firmware for the module, and for this, there are two approaches; the first one is an MCO that patches the kernel command line (and will cause a reboot of the node) as configured with the following YAML:
apiVersion: machineconfiguration.openshift.io/v1
kind: MachineConfig
metadata:
labels:
machineconfiguration.openshift.io/role: master
name: 100-alternative-fw-path-for-master-nodes
spec:
config:
ignition:
version: 3.2.0
kernelArguments:
- firmware_class.path=/var/lib/firmware
And for validating, we can check on the hosts via oc debug node/rhvp-intel-01
that the new parameter has been enabled:
$ cat /proc/cmdline
BOOT_IMAGE=(hd5,gpt3)/ostree/rhcos-085fdd39288474060c9d5bd7a88fabe8d218fcc960186712834c5e4ab319cb1d/vmlinuz-5.14.0-284.52.1.el9_2.x86_64 ignition.platform.id=metal ostree=/ostree/boot.0/rhcos/085fdd39288474060c9d5bd7a88fabe8d218fcc960186712834c5e4ab319cb1d/0 ip=ens801f0:dhcp,dhcp6 root=UUID=bf0c9edf-4aab-48a8-9549-5005fff7890e rw rootflags=prjquota boot=UUID=282ee60b-3053-4c2e-8f92-612af621e245 firmware_class.path=/var/lib/firmware systemd.unified_cgroup_hierarchy=1 cgroup_no_v1=all psi=1
The other approach is to patch the configuration for the KMM operator.
Alternatively, we can modify the configmap
by setting the path for the firmware which doesn’t cause a reboot, which is useful in single node OpenShift installations:
$ oc patch configmap kmm-operator-manager-config -n openshift-kmm --type='json' -p='[{"op": "add", "path": "/data/controller_config.yaml", "value": "healthProbeBindAddress: :8081\nmetricsBindAddress: 127.0.0.1:8080\nleaderElection:\n enabled: true\n resourceID: kmm.sigs.x-k8s.io\nwebhook:\n disableHTTP2: true\n port: 9443\nworker:\n runAsUser: 0\n seLinuxType: spc_t\n setFirmwareClassPath: /var/lib/firmware"}]'
If you follow this approach, the KMM pod must be deleted so that the new configuration is taken into effect as soon as the operator recreates it, so that the systems get it applied during the loading of the modules.
Intel Data Center GPU Flex 140 kernel module
The Intel GPU kernel module deployment might be a bit tricky because it’s a driver and it has more ties to the specific kernel version being used that we’ll be setting in the commands for the build process, as we’ll see in the Containerfile used for the build.
Using prebuilt drivers
In this approach, we use KMM to deploy the built and certified drivers already created by Intel which provides the container for each kernel version via a CI/CD pipeline. End users can directly consume that container via this Module definition:
apiVersion: kmm.sigs.x-k8s.io/v1beta1
kind: Module
metadata:
name: intel-dgpu
namespace: openshift-kmm
spec:
moduleLoader:
container:
modprobe:
moduleName: i915
firmwarePath: /firmware
kernelMappings:
- regexp: '^.*\.x86_64$'
containerImage: registry.connect.redhat.com/intel/intel-data-center-gpu-driver-container:2.2.0-$KERNEL_FULL_VERSION
selector:
intel.feature.node.kubernetes.io/gpu: "true"
Compiling your own driver
For the sake of demonstration, here we’ll be building our own driver image using in-cluster builds.
For building the kernel module, we can use the following Containerfile based on the upstream instructions that we’re already defining as configmap:
apiVersion: v1
kind: ConfigMap
metadata:
name: intel-dgpu-dockerfile-configmap
namespace: openshift-kmm
data:
dockerfile: |-
# Intel Data Center GPU driver components combinations.
ARG I915_RELEASE=I915_23WW51.5_682.48_23.6.42_230425.56
ARG FIRMWARE_RELEASE=23WW49.5_682.48
# Intel Data Center GPU Driver for OpenShift version.
ARG DRIVER_VERSION=2.2.0
# RHCOS Kernel version supported by the above driver version.
ARG KERNEL_FULL_VERSION
# Red Hat DTK image is used as builder image to build kernel driver modules.
# Appropriate DTK image is provided with the OCP release, to guarantee compatibility
# between the built kernel modules and the OCP version's RHCOS kernel.
# DTK_AUTO is populated automatically with the appropriate DTK image by KMM operator.
ARG DTK_AUTO
FROM ${DTK_AUTO} as builder
ARG I915_RELEASE
ARG FIRMWARE_RELEASE
ARG KERNEL_FULL_VERSION
WORKDIR /build
# Building i915 driver
RUN git clone -b ${I915_RELEASE} --single-branch https://github.com/intel-gpu/intel-gpu-i915-backports.git \
&& cd intel-gpu-i915-backports \
&& install -D COPYING /licenses/i915/COPYING \
&& export LEX=flex; export YACC=bison \
&& export OS_TYPE=rhel_9 && export OS_VERSION="9.2" \
&& cp defconfigs/i915 .config \
&& make olddefconfig && make modules -j $(nproc) && make modules_install
# Copy out-of-tree drivers to /opt/lib/modules/${KERNEL_FULL_VERSION}/
RUN for file in $(find /lib/modules/${KERNEL_FULL_VERSION}/updates/ -name "*.ko"); do \
cp $file /opt --parents; done
# Create the symbolic link for in-tree dependencies
RUN ln -s /lib/modules/${KERNEL_FULL_VERSION} /opt/lib/modules/${KERNEL_FULL_VERSION}/host
RUN depmod -b /opt ${KERNEL_FULL_VERSION}
# Firmware
RUN git clone -b ${FIRMWARE_RELEASE} --single-branch https://github.com/intel-gpu/intel-gpu-firmware.git \
&& install -D /build/intel-gpu-firmware/COPYRIGHT /licenses/firmware/COPYRIGHT \
&& install -D /build/intel-gpu-firmware/COPYRIGHT /build/firmware/license/COPYRIGHT \
&& install -D /build/intel-gpu-firmware/firmware/dg2* /build/firmware/ \
&& install -D /build/intel-gpu-firmware/firmware/pvc* /build/firmware/
# Packaging Intel GPU driver components in the base UBI image for certification
FROM registry.redhat.io/ubi9/ubi-minimal:9.2
ARG DRIVER_VERSION
ARG KERNEL_FULL_VERSION
ARG I915_RELEASE
ARG FIRMWARE_RELEASE
# Required labels for the image metadata
LABEL vendor="Intel®"
LABEL version="${DRIVER_VERSION}"
LABEL release="${KERNEL_FULL_VERSION}"
LABEL name="intel-data-center-gpu-driver-container"
LABEL summary="Intel® Data Center GPU Driver Container Image"
LABEL description="Intel® Data Center GPU Driver container image designed for Red Hat OpenShift Container Platform. \
The driver container is based on Intel Data Center GPU driver components - i915 driver release:${I915_RELEASE}, \
and Firmware release:${FIRMWARE_RELEASE}. This driver container image is supported for RHOCP 4.14 RHCOS kernel version: ${KERNEL_FULL_VERSION}."
RUN microdnf update -y && rm -rf /var/cache/yum
RUN microdnf -y install kmod findutils && microdnf clean all
COPY --from=builder /licenses/ /licenses/
COPY --from=builder /opt/lib/modules/${KERNEL_FULL_VERSION}/ /opt/lib/modules/${KERNEL_FULL_VERSION}/
COPY --from=builder /build/firmware/ /firmware/i915/
Let’s also define the imagestream
for storing the generated driver image:
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
labels:
app: intel-dgpu-driver-container-kmmo
name: intel-dgpu-driver-container-kmmo
namespace: openshift-kmm
spec: {}
Reference this Containerfile in the following YAML:
---
apiVersion: kmm.sigs.x-k8s.io/v1beta1
kind: Module
metadata:
name: intel-dgpu-on-premise
namespace: openshift-kmm
spec:
moduleLoader:
container:
imagePullPolicy: Always
modprobe:
moduleName: i915
firmwarePath: /firmware
kernelMappings:
- regexp: '^.*\.x86_64$'
containerImage: image-registry.openshift-image-registry.svc:5000/openshift-kmm/intel-dgpu-driver-container-kmmo:$KERNEL_FULL_VERSION
build:
dockerfileConfigMap:
name: intel-dgpu-dockerfile-configmap
selector:
intel.feature.node.kubernetes.io/gpu: "true"
This file will use the above Containerfile to build the module and store the image in the repository. Note the selector
field. It has been modified to use NFD for discovery and only load the kernel module where needed.
We can check that the kernel module has been loaded by connecting to the node and checking the status:
sh-5.1# lsmod|grep i915
i915 3977216 0
intel_vsec 20480 1 i915
compat 24576 2 intel_vsec,i915
video 61440 1 i915
drm_display_helper 172032 2 compat,i915
cec 61440 2 drm_display_helper,i915
i2c_algo_bit 16384 2 ast,i915
drm_kms_helper 192512 5 ast,drm_display_helper,i915
drm 581632 7 drm_kms_helper,compat,ast,drm_shmem_helper,drm_display_helper,i915
Alternatively, we can check the label added by KMM on the nodes:
$ oc describe node |grep kmm
kmm.node.kubernetes.io/openshift-kmm.intel-dgpu-on-premise.ready=
Once the kernel module is deployed, use an application to verify HW acceleration is provided by the GPU.
Note
Here we’re directly using the /dev
filesystem for accessing the GPU. The recommended way is to use the Intel Device Plugins Operator and then add a CR to expose gpu.intel.com/i915 to the kubelet for workload consumption as described in the repository.
Verify the deployment
Simple approach
We’ll be using clinfo
to get information from our card. To do so, we’ll create the image and the namespace and then run the utility inside a privileged pod similar to the application that we can use as a more complex approach.
Let’s create the BuildConfiguration
and ImageStream
based on this one by creating a clinfobuild.yaml
:
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: intel-dgpu-clinfo
namespace: openshift-kmm
spec: {}
---
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
name: intel-dgpu-clinfo
namespace: openshift-kmm
spec:
output:
to:
kind: ImageStreamTag
name: intel-dgpu-clinfo:latest
runPolicy: Serial
source:
dockerfile:
"ARG BUILDER=registry.access.redhat.com/ubi9-minimal:latest \nFROM
${BUILDER} \n\nARG OCL_ICD_VERSION=ocl-icd-2.2.13-4.el9.x86_64\nARG CLINFO_VERSION=clinfo-3.0.21.02.21-4.el9.x86_64\n\nRUN
microdnf install -y \\\n glibc \\\n yum-utils \n\n# install intel-opencl,
ocl-icd and clinfo\nRUN dnf install -y 'dnf-command(config-manager)' && \\\n
\ dnf config-manager --add-repo https://repositories.intel.com/gpu/rhel/9.0/lts/2350/unified/intel-gpu-9.0.repo
&& \\\n dnf install -y intel-opencl \\\n https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages/$OCL_ICD_VERSION.rpm
\ \\\n https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/Packages/c/$CLINFO_VERSION.rpm
&& \\\n dnf clean all && dnf autoremove && rm -rf /var/lib/dnf/lists/* && \\\n
\ rm -rf /etc/yum.repos.d/intel-graphics.repo \n"
type: Dockerfile
strategy:
dockerStrategy:
buildArgs:
- name: BUILDER
value: registry.access.redhat.com/ubi9-minimal:latest
- name: OCL_ICD_VERSION
value: ocl-icd-2.2.13-4.el9.x86_64
- name: CLINFO_VERSION
value: clinfo-3.0.21.02.21-4.el9.x86_64
type: Docker
triggers:
- type: ConfigChange
Let’s apply it with:
$ oc create -f clinfobuild.yaml
And then define the privileged pod that will run the tool with this pod defined by job.yaml
:
apiVersion: v1
kind: Pod
metadata:
name: intel-dgpu-clinfo
spec:
containers:
- name: clinfo-pod
image: image-registry.openshift-image-registry.svc:5000/openshift-kmm/intel-dgpu-clinfo:latest
command: ["clinfo"]
resources:
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 110
volumeMounts:
- name: dev
mountPath: /dev
volumes:
- name: dev
hostPath:
path: /dev
Let’s create the pod with:
$ oc create -f job.yaml
And then examine the output of the pod by running:
$ oc logs pod intel-dgpu-clinfo
Number of platforms 1
Platform Name Intel(R) OpenCL HD Graphics
Platform Vendor Intel(R) Corporation
Platform Version OpenCL 3.0
Platform Profile FULL_PROFILE
...
...
Platform Name Intel(R) OpenCL HD Graphics
Number of devices 1
Device Name Intel(R) Data Center GPU Flex Series 140 [0x56c1]
Device Vendor Intel(R) Corporation
Device Vendor ID 0x8086
Device Version OpenCL 3.0 NEO
...
...
Platform Name Intel(R) OpenCL HD Graphics
Device Name Intel(R) Data Center GPU Flex Series 140 [0x56c1]
ICD loader properties
ICD loader Name OpenCL ICD Loader
ICD loader Vendor OCL Icd free software
ICD loader Version 2.2.12
ICD loader Profile OpenCL 2.2
NOTE: your OpenCL library only supports OpenCL 2.2,
but some installed platforms support OpenCL 3.0.
Programs using 3.0 features may crash
or behave unexpectedly
Using OpenVINO application for image text to image with stable diffusion
An application using Intel’s OpenVINO software will be used to showcase the functionality of the GPU acceleration in the processing that will use some keywords introduced to generate images.
Requirements
In the following paragraphs we’ll be covering the requirements that we’ll be preparing for the final step of validating the proper setup of our driver.
ImageStream
We need to define an ImageStream to store our container with this YAML:
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
labels:
app: jupyter-demo
name: jupyter-demo
namespace: openshift-kmm
spec: {}
Containerfile
First prepare the container image containing all the required bits and pieces for storing in a registry for later use with this BuildConfig:
kind: BuildConfig
apiVersion: build.openshift.io/v1
metadata:
name: "jupyter-demo"
spec:
source:
dockerfile: |
FROM quay.io/jupyter/base-notebook
USER root
RUN apt update && \
apt install -y gpg-agent git wget && \
apt clean
RUN wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg
RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/gpu/ubuntu jammy/production/2328 unified" > /etc/apt/sources.list.d/intel-gpu-jammy.list
RUN apt update && \
apt install -y \
intel-opencl-icd intel-level-zero-gpu level-zero \
intel-media-va-driver-non-free libmfx1 libmfxgen1 libvpl2 \
libegl-mesa0 libegl1-mesa libegl1-mesa-dev libgbm1 libgl1-mesa-dev libgl1-mesa-dri \
libglapi-mesa libgles2-mesa-dev libglx-mesa0 libigdgmm12 libxatracker2 mesa-va-drivers \
mesa-vdpau-drivers mesa-vulkan-drivers va-driver-all vainfo hwinfo clinfo \
libglib2.0-0 && \
apt clean
USER jovyan
RUN pip install --no-cache-dir "diffusers>=0.14.0" "openvino>=2023.3.0" "transformers >= 4.31" accelerate "urllib3==1.26.15" ipywidgets opencv-python scipy
RUN mkdir -p /home/jovyan/.cache/huggingface
strategy:
type: Docker
output:
to:
kind: ImageStreamTag
name: jupyter-demo:latest
Note
In the above BuildConfig
, there might be newer versions of the software installed by pip. It may be necessary to update and use a newer version. We’re going to use OpenVINO project notebooks, which already execute some pip commands to install required libraries in any case.
Let’s now start the build of the image with:
$ oc start-build jupyter-demo
And once it’s finished, we can check that the image appears with:
$ oc get is
NAME IMAGE REPOSITORY TAGS UPDATED
jupyter-demo image-registry.openshift-image-registry.svc:5000/openshift-kmm/jupyter-demo latest 2 minutes ago
If we want to check the builds with oc logs -f build/<<buildname>
, we’ll see an output similar to this one:
$ oc logs -f build/jupyter-demo-1
time="2024-03-13T12:50:47Z" level=info msg="Not using native diff for overlay, this may cause degraded performance for building images: kernel has CONFIG_OVERLAY_FS_REDIRECT_DIR enabled"
I0313 12:50:47.551349 1 defaults.go:112] Defaulting to storage driver "overlay" with options [mountopt=metacopy=on].
Caching blobs under "/var/cache/blobs".
Pulling image quay.io/jupyter/base-notebook ...
Trying to pull quay.io/jupyter/base-notebook:latest...
Getting image source signatures
...
...
STEP 9/11: RUN mkdir -p /home/jovyan/.cache/huggingface
--> 284cd3e642a7
STEP 10/11: ENV "OPENSHIFT_BUILD_NAME"="jupyter-demo-1" "OPENSHIFT_BUILD_NAMESPACE"="openshift-kmm"
--> 9bba674b8144
STEP 11/11: LABEL "io.openshift.build.name"="jupyter-demo-1" "io.openshift.build.namespace"="openshift-kmm"
COMMIT temp.builder.openshift.io/openshift-kmm/jupyter-demo-1:d93bad68
--> c738f8d15e38
Successfully tagged temp.builder.openshift.io/openshift-kmm/jupyter-demo-1:d93bad68
c738f8d15e38ea41f9a17082a435b4e8badf2e7d0569f34c332cf09102b0992d
Pushing image image-registry.openshift-image-registry.svc:5000/openshift-kmm/jupyter-demo:latest ...
Getting image source signatures
Copying blob sha256:c37f7a4129892837c4258c045d773d933f9307d7dcf6801d80a2903c38e7936c
...
...
sha256:59ebd409476f3946cadfccbea9e851574c50b8ef6959f62bdfa2dd708423da30
Copying config sha256:c738f8d15e38ea41f9a17082a435b4e8badf2e7d0569f34c332cf09102b0992d
Writing manifest to image destination
Successfully pushed image-registry.openshift-image-registry.svc:5000/openshift-kmm/jupyter-demo@sha256:f9ee5ae8fa9db556e90908b278c7ebb2d2ad271e11da82cfad44620d65834bf8
Push successful
We need to define a StorageClass to use an LVM for the underlying storage. Then, a volume creation with persistent storage (PVC) is registered so that the space is allocated and prepared for our application and a secondary one for the cache of the application.
We’ll use that space later on to download the Jupyter notebooks that we’ll be using for the GPU demonstration.
For the following items, apply each one with:
$ oc apply -f <file.yaml>
StorageClass
Let’s create a file with the following contents defining our Storage Class named sc.yaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: lvms-vg1
parameters:
csi.storage.k8s.io/fstype: xfs
topolvm.io/device-class: vg1
provisioner: topolvm.io
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
Persistent Volume Claim
Similarly, we need to create a file for registering the storage that we’ll be naming pvc.yaml
with the following contents:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
spec:
storageClassName: lvms-vg1
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
PVC for cache
Finally, the application uses some cache, so another PVC will be created using a file named cache.yaml
with the following contents:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: huggingface-cache
spec:
storageClassName: lvms-vg1
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
Make sure you’ve applied the above files as instructed before following from this point.
At this point, we’ve created the Build, ImageStream, StorageClass, PVC, and PVC for Cache; because we launched the build, we will also have the ImageStream populated, so we’re ready to run the application.
Running the application
A Jupyter notebook is an interactive workbook where commands, outputs like visualizations, etc., can be shown alongside the code.
It’s commonly used in data science analysis as it allows you to quickly edit and amend the commands and refresh the output with the new values.
Using the image generated in the previous Containerfile and the previous storage PVCs, we can create a pod with this YAML:
apiVersion: v1
kind: Pod
metadata:
name: kmm-demo-jupyter
spec:
containers:
- name: kmm-demo-jupyter
image: image-registry.openshift-image-registry.svc:5000/openshift-kmm/jupyter-demo
args:
# Password is paella in case you want to reuse
- start-notebook.py
- --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$00Ynt8+Jk4sMtJUM+7Us5Q$ycb5PzmA7IH9yfOPAIfUjMNvDzXHKiMXPvM6+R5nucQ'
env:
- name: GRANT_SUDO
value: "yes"
- name: NB_GID
value: "110"
ports:
- containerPort: 8888
hostPort: 8888
resources:
limits:
cpu: "20"
memory: "64Gi"
securityContext:
privileged: true
runAsUser: 0
runAsGroup: 110
volumeMounts:
- name: dev
mountPath: /dev
- name: huggingface-cache
mountPath: /home/jovyan/.cache/huggingface
- name: work
mountPath: /home/jovyan/work
volumes:
- name: dev
hostPath:
path: /dev
- name: huggingface-cache
persistentVolumeClaim:
claimName: huggingface-cache
- name: work
persistentVolumeClaim:
claimName: pvc
Once the app is ready we can forward a local port (for example, to reach the application), but first we’ll prepare the examples we’ll be using with the Jupyter notebooks from the OpenVINO project by getting inside our pod with:
$ oc rsh kmm-demo-jupyter
…then, once we’re inside the pod:
$ pwd
/home/jovyan
$ ls
work
$ git clone https://github.com/openvinotoolkit/openvino_notebooks
chown -R jovyan:users openvino_notebooks
Note
If you don’t specify an authentication method on the pod, a token will be printed on the pod logs, that should be used when reaching the Jupyter interface.
Using the following command, we’ll be forwarding a port from your computer to the pod itself so it will be easier to interact with the Jupyter notebook running there:
$ oc port-forward kmm-demo-jupyter 8888:8888
Once done, on your local computer browser open http://localhost:8888
to access the notebook.
In the example above, the hashed password is paella
, and it’s the one we’ll be using to access the Jupyter notebook.
From within the browser, you can access the previous URL, navigate the notebooks, and select 225-stable-diffusion-text-to-image
so that the final URL is:
http://localhost:8888/lab/tree/openvino_notebooks/notebooks/225-stable-diffusion-text-to-image/225-stable-diffusion-text-to-image.ipynb
Skip over the explanation steps and navigate to the area with the GPU selection drop-down (Figure 1).
Figure 1: GPU selection dropdown
…and later, the keyword section (Figure 2).
Figure 2: keyword selection for imput
In this section, you can describe what kind of image should be generated. This is where the real magic happens. In this case, we will use the prompt Valencia fallas sunny day
and see what kind of image is generated. See Figure 3.
Figure 3: Output of the pipeline for the initial set of words Of course, you can go back, edit the input keywords, and try new ones (Figure 4)
Figure 4: Output of the pipeline for the additional set of words
Wrap up
We hope that you’ve enjoyed this read and realize how KMM, NFD, and DTK make managing custom drivers across many nodes across a cluster much easier than having to log into each node individually to install drivers.
We hope that you’ve enjoyed this read and realize how quick and convenient it is to use KMM, NFD, and DTK to enable support for accelerators in your OpenShift infrastructure.
But I do still want to upgrade my cluster!
Don’t worry, KMM automatically will check the modules configured for your hosts and the kernel they are running—if you’re using the prebuilt images, KMM will download and enable the prebuilt image and if you’re using a custom build, a new build process will happen so that the new image is available… and using the labels added by KMM you can schedule your workloads on the nodes that have the driver ready for consumption.
It’s still recommended to do a staggered upgrade, so only a few nodes are updated before moving into others to avoid, for example, a new kernel having some issues with the build process or no prebuilt driver being available because it is still under the certification and validation process… rendering workloads requiring a specific device driver to become unschedulable.
Once you’ve checked that the driver is available at the preceding link and checked the official documentation on the OpenShift Upgrade process, be ready!
Enjoy! (and if you do, you can Buy Me a Coffee )