広告 プログラミング

【解説】Docker上にCodon+JupyterLab環境を構築する

※本ページには、プロモーション(広告)が含まれています。

悩んでいる人

DockerでJupyterLab環境を構築した。

この環境にCodonを追加したいため、Codon+JupyterLab環境を構築する方法を教えて欲しい。

こんなお悩みを解決します。

以前、Docker上に「Codon」の環境を構築する方法について解説しました。

あわせて読みたい
【解説】DockerによるPythonコンパイラ「Codon」の環境構築方法

続きを見る

今回は、CodonをJupyterLab上で利用するための方法について解説します。

公式サイトに紹介されている手順を参考に解説していますので、具体的な手順を知りたい方は、ぜひ最後までご覧ください。

動作構築

Codon+JupyterLabの動作環境は、以下のようになります。

項目内容
OSLinux
ディストリビューションUbuntu 22.04.1 LTS
32bit/64bit64bit(x86_64)
動作環境

詳細は、以下の記事を参照してください。

あわせて読みたい
【解説】Linuxサーバの構築方法を分かりやすく解説!

続きを見る

ベースとするJupyterLab環境

JupyterLabの環境構築は、以前の記事をベースとします。

あわせて読みたい
【Python】機械学習を用いた競馬予想【環境構築編】

続きを見る

環境構築

以降に、具体的な環境構築方法について解説します。

ディレクトリ構成

今回の環境構築に関連するファイル一式を以下に示します。


./
|-- Dockerfile
|-- docker-compose.yml
|-- config/
|   |-- tracker.jupyterlab-settings
|   `-- plugin.jupyterlab-settings
`-- workspace/
    `-- ...

また、Dockerfileの中で登場する「entrypoint.sh」は、上記のGitHubのプロジェクトや関連記事をご覧ください。

Dockerfile

今回のメイン部分となるDockerfileの内容について解説します。

まず、Dockerfileの全体像を以下に示します。

FROM python:3.9.7-buster
ARG DEBIAN_FRONTEND=noninteractive

ENV TZ=Asia/Tokyo \
    CMAKE_VERSION=3.26.3 \
    LLVM_VERSION=15 \
    CODON_INSTALL_PREFIX=/usr/local

# Install packages and setup timezone
RUN    apt-get update \
    && apt-get install -y tzdata \
    && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \
    && useradd -m labuser \
    && mkdir -p /home/labuser/work \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && python3 -m pip install --upgrade pip

# Install python packages
RUN    apt-get update \
    && apt-get install -y cargo \
    \
    # install jupyter packages
    \
    && pip install --no-cache-dir \
        black \
        jupyterlab \
        jupyterlab_code_formatter \
        jupyterlab-git \
        lckr-jupyterlab-variableinspector \
        jupyterlab_widgets \
        ipywidgets \
        import-ipynb \
    && pip install --no-cache-dir \
    \
    # install basic packages
    \
        numpy \
        pandas \
        scipy \
        scikit-learn \
        pycaret \
        xfeat \
        featuretools \
        matplotlib \
        japanize_matplotlib \
        mlxtend \
        seaborn \
        plotly \
        requests \
        beautifulsoup4 \
        lxml \
        html5lib \
        Pillow \
        opencv-python \
    \
    # install additional packages
    \
        pydeps \
        graphviz \
        pandas_profiling \
        shap \
        umap \
        xgboost \
        optuna \
        hyperopt \
        MonthDelta \
        lightgbm \
        pandarallel \
        joblib \
    \
    # install pytorch (cpu version)
    \
    && pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cpu \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Install LLVM and Codon
RUN    apt-get update \
    && apt-get install -y lsb-release wget software-properties-common gnupg git \
                          ninja-build libxml2-dev libsodium-dev uuid-dev libssl-dev \
    \
    # install cmake
    \
    && cd /tmp \
    && curl -sSL https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-$(uname -m).sh -o cmake-install.sh \
    && chmod +x cmake-install.sh \
    && ./cmake-install.sh --prefix=/usr/local --exclude-subdir --skip-license \
    && cd / \
    \
    # install LLVM
    \
    && cd /tmp \
    && wget https://apt.llvm.org/llvm.sh -O llvm.sh \
    && chmod +x llvm.sh \
    && ./llvm.sh ${LLVM_VERSION} all \
    && update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 1 \
    && update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 1 \
    && update-alternatives --install /usr/bin/llvm-config llvm-config++ /usr/bin/llvm-config-${LLVM_VERSION} 1 \
    \
    # install Codon
    \
    && git clone https://github.com/exaloop/codon.git \
    && cd codon \
    && cmake -S . -B build -G Ninja \
             -DCMAKE_BUILD_TYPE=Release \
             -DLLVM_DIR=$(llvm-config --cmakedir) \
             -DCMAKE_C_COMPILER=clang \
             -DCMAKE_CXX_COMPILER=clang++ \
    && cmake --build build \
    && cmake --install build --prefix=${CODON_INSTALL_PREFIX} \
    \
    # install jupyter plugin
    \
    && cmake -S jupyter -B jupyter/build -G Ninja \
             -DCMAKE_BUILD_TYPE=Release \
             -DCMAKE_C_COMPILER=clang \
             -DCMAKE_CXX_COMPILER=clang++ \
             -DLLVM_DIR=$(llvm-config --cmakedir) \
             -DCODON_PATH=${CODON_INSTALL_PREFIX} \
             -DOPENSSL_ROOT_DIR=$(openssl version -d | cut -d' ' -f2 | tr -d '"') \
             -DOPENSSL_CRYPTO_LIBRARY=$(ldconfig -p | grep "libssl.so$" | cut -d' ' -f4) \
             -DXEUS_USE_DYNAMIC_UUID=ON \
    && cmake --build jupyter/build \
    && cmake --install jupyter/build \
    \
    # setup jupyter kernel
    \
    && target_dir=$(jupyter kernelspec list | grep python | tr -s "[:space:]" | cut -d' ' -f3 | grep -oP "(.*)(?=/python)")/codon \
    && mkdir -p ${target_dir} \
    && echo '{"display_name":"Codon","argv":["'${CODON_INSTALL_PREFIX}'/bin/codon","jupyter","{connection_file}"],"language":"python"}' > ${target_dir}/kernel.json \
    && cd / \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/*

COPY ./entrypoint.sh /entrypoint.sh
RUN    chmod +x /entrypoint.sh \
    && mkdir -p /home/labuser/.jupyter/lab/user-settings/@jupyterlab/notebook-extension \
    && mkdir -p /home/labuser/.jupyter/lab/user-settings/@jupyterlab/fileeditor-extension \
    && chown labuser:labuser -R /home/labuser/.jupyter/lab

ENTRYPOINT ["/entrypoint.sh"]
CMD ["jupyter-lab", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--NotebookApp.token=''", "--notebook-dir=/home/labuser/work"]

上記のうち、変化点は、以下の4点となります。

  • cmakeのインストール
  • LLVMのインストール
  • Codonのインストール
  • CodonのJupyter Pluginのインストール

以降では、上記のそれぞれに対し、具体的な実施内容について解説します。

また、Codonをソースコードからインストールする都合上、cmakeLLVMのバージョンは、以下を満たす必要があります。

対象バージョンに関する制約
cmakeversion 3.14以上
LLVMversion 15以上
cmakeとLLVMに対するバージョンの制約

これらのバージョンを変更したい場合、Dockerfileの以下の箇所を変更してください。

CMAKE_VERSION=3.26.3 # CMAKEのバージョン
LLVM_VERSION=15      # LLVMのバージョン

cmakeのインストール

cmakeは、バイナリ版のインストーラーを利用します。

インストール時のオプションは、以下のようになります。

オプション説明設定値
--prefixインストール先/usr/local
--exclude-subdirサブディレクトリの構成なし(インストール先の直下にbinlibを配置する)
--skip-licenseライセンスに関する承諾事項の省略なし
インストール時のオプション

Docker上で環境構築を行うため基本的に変更は不要ですが、設定を変更したい場合、以下の箇所を修正してください。

./cmake-install.sh --prefix=/usr/local --exclude-subdir --skip-license

LLVMのインストール

LLVMもバイナリ版のインストーラーを利用します。

Codonの公式サイトでは、LLVMもソースコードからインストールする方法が紹介されていますが、非常に時間がかかるため、バイナリ版を用いて所要時間を短縮します。

LLVMには、オートインストーラーが付属しているため、今回は、コチラを利用しましょう。

また、同時にインストールされるコンパイラ(clangやclang++など)は、LLVMのバージョン番号が付与されますが、扱いやすさを考慮し、名称を更新しておきます。

Dockerfile上では、以下がインストール処理に該当する部分となります。

cd /tmp \
&& wget https://apt.llvm.org/llvm.sh -O llvm.sh \
&& chmod +x llvm.sh \
\
# LLVMのインストール
\
&& ./llvm.sh ${LLVM_VERSION} all \
\
# clang、clang++、llvm-configの名称更新
\
&& update-alternatives --install /usr/bin/clang clang /usr/bin/clang-${LLVM_VERSION} 1 \
&& update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-${LLVM_VERSION} 1 \
&& update-alternatives --install /usr/bin/llvm-config llvm-config++ /usr/bin/llvm-config-${LLVM_VERSION} 1

Codonのインストール

続いて、Codonのインストールを行います。

Codonは、ソースコードからインストールする必要があるため、先程インストールしたcmakeコマンドを用いて作業を進めます。

インストール時のオプションは、公式サイトをベースにしていますが、prefixだけは以下のようにカスタマイズしています。

オプションの変更箇所変更内容
--prefix・変更前:install
・変更後:/usr/local
オプションの変更箇所

上記の設定の場合、以下のような階層構造でCodonがインストールされます。


/usr/local/
|-- bin/
|   |-- codon/
|   |   `-- ...
|   `-- ...
|-- include/
|   |-- codon/
|   |   `-- ...
|   `-- ...
`-- lib/
    |-- codon/
    |   `-- ...
    `-- ..

また、インストール先を変更したい場合、Dockerfileの以下の部分を修正してください。

CODON_INSTALL_PREFIX=/usr/local # ここを必要に応じて修正する

CodonのJupyterLab Pluginのインストール

最後に、CodonのJupyterLab Pluginをインストールします。

コード上の該当部分を以下に示します。

cmake -S jupyter -B jupyter/build -G Ninja \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_C_COMPILER=clang \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DLLVM_DIR=$(llvm-config --cmakedir) \
    -DCODON_PATH=${CODON_INSTALL_PREFIX} \
    -DOPENSSL_ROOT_DIR=$(openssl version -d | cut -d' ' -f2 | tr -d '"') \
    -DOPENSSL_CRYPTO_LIBRARY=$(ldconfig -p | grep "libssl.so$" | cut -d' ' -f4) \
    -DXEUS_USE_DYNAMIC_UUID=ON

後は、ホストマシン上で以下のコマンドを実行し、docker imageが作成されるまで、気長に待ちます。

# docker-compose.ymlがあるディレクトリで以下を実行
docker-compose build --no-cache

entrypoint.sh

また、Dockerfile中にあるentrypoint.shのスクリプトの内容は、以下のようになります。

#!/bin/bash

export USER=labuser
export HOME=/home/${USER}

# Check PUID and PGID of environment variable
if [ -n "${PUID}" ] && [ -n "${PGID}" ]; then

    # Check value of PUID
    if [ ${PUID} -ne 0 ]; then
        uid=$(id -u ${USER})
        gid=$(id -g ${USER})

        # In the case of gid is not equal to PGID
        if [ ${gid} -ne ${PGID} ]; then
            getent group ${PGID} > /dev/null 2>&1 || groupmod -g ${PGID} ${USER}
            chgrp -R ${PGID} ${HOME}
        fi
        # In the case of uid is not equal to PUID
        if [ ${uid} -ne ${PUID} ]; then
            usermod -u ${PUID} ${USER}
        fi
    fi
fi

# Run the command as an USER
exec setpriv --reuid=${USER} --regid=${USER} --init-groups "$@"

docker-compose.yml

docker-composeコマンドを用いて、コンテナの管理を行う方が多いと思います。

今回の変更にあわせてアップデートしている部分を以下に示します。

version: '3.7'

x-logging:
    &default-json-logging
    driver: json-file
    options:
        max-file: "3"
        max-size: "10m"

services:
  jupyterlab:
    build:
      context: .
      dockerfile: Dockerfile
    image: jupyterlab
    # Docker環境に共有するメモリサイズを指定
    shm_size: '3gb'
    restart: always
    container_name: jupyterlab
    environment:
      - PUID=1000
      - PGID=1000
      - CODON_PYTHON=/usr/local/lib/libpython3.9.so
    volumes:
      - ./workspace:/home/labuser/work
      # 以下2行を追加
      - ./config/tracker.jupyterlab-settings:/home/labuser/.jupyter/lab/user-settings/@jupyterlab/notebook-extension/tracker.jupyterlab-settings
      - ./config/plugin.jupyterlab-settings:/home/labuser/.jupyter/lab/user-settings/@jupyterlab/fileeditor-extension/plugin.jupyterlab-settings
    ports:
      - 18580:8888
    logging: *default-json-logging

JupyterLabで利用するsettingファイルの内容

参考までに、私が利用しているsettingファイルを記載しておきます。

具体的には、文字サイズや1行の文字数をカスタマイズしています。

tracker.jupyterlab-settings

{
    "codeCellConfig": {
        "autoClosingBrackets": false,
        "cursorBlinkRate": 530,
        "fontFamily": null,
        "fontSize": 20,
        "lineHeight": null,
        "lineNumbers": true,
        "lineWrap": "off",
        "matchBrackets": true,
        "readOnly": false,
        "insertSpaces": true,
        "tabSize": 4,
        "wordWrapColumn": 80,
        "rulers": [],
        "codeFolding": false,
        "lineWiseCopyCut": true,
        "showTrailingSpace": false
    },
    "defaultCell": "code",
    "kernelShutdown": false,
    "markdownCellConfig": {
        "autoClosingBrackets": false,
        "cursorBlinkRate": 530,
        "fontFamily": null,
        "fontSize": 20,
        "lineHeight": null,
        "lineNumbers": true,
        "lineWrap": "on",
        "matchBrackets": false,
        "readOnly": false,
        "insertSpaces": true,
        "tabSize": 4,
        "wordWrapColumn": 80,
        "rulers": [],
        "codeFolding": false,
        "lineWiseCopyCut": true,
        "showTrailingSpace": false
    },
    "rawCellConfig": {
        "autoClosingBrackets": false,
        "cursorBlinkRate": 530,
        "fontFamily": null,
        "fontSize": 20,
        "lineHeight": null,
        "lineNumbers": true,
        "lineWrap": "on",
        "matchBrackets": false,
        "readOnly": false,
        "insertSpaces": true,
        "tabSize": 4,
        "wordWrapColumn": 80,
        "rulers": [],
        "codeFolding": false,
        "lineWiseCopyCut": true,
        "showTrailingSpace": false
    },
    "scrollPastEnd": true,
    "recordTiming": false,
    "numberCellsToRenderDirectly": 99999,
    "remainingTimeBeforeRescheduling": 50,
    "renderCellOnIdle": true,
    "observedTopMargin": "1000px",
    "observedBottomMargin": "1000px",
    "maxNumberOutputs": 50,
    "showEditorForReadOnlyMarkdown": true,
    "kernelStatus": {
        "showOnStatusBar": false,
        "showProgress": true
    },
    "experimentalDisableDocumentWideUndoRedo": false,
    "renderingLayout": "default",
    "sideBySideLeftMarginOverride": "10px",
    "sideBySideRightMarginOverride": "10px",
    "sideBySideOutputRatio": 1
}

plugin.jupyterlab-settings

{
    "editorConfig": {
        "autoClosingBrackets": false,
        "codeFolding": false,
        "cursorBlinkRate": 530,
        "fontFamily": null,
        "fontSize": 20,
        "insertSpaces": true,
        "lineHeight": null,
        "lineNumbers": true,
        "lineWrap": "on",
        "matchBrackets": true,
        "readOnly": false,
        "rulers": [],
        "showTrailingSpace": false,
        "tabSize": 4,
        "wordWrapColumn": 80
    },
    "toolbar": []
}

動作確認

実際にJupyterLabでCodonを利用してみたいと思います。

Codon版のノートブック作成

JupyterLabを起動すると、以下のような画面が表示されるので、Codonと表示されている部分を押下します。

今回の環境構築の方法では、以下のようなエラーが表示されますが、サンプルを動作させる上では無関係だったので、一旦無視します。

表示されたダイアログの「OK」を押下します。

ちなみに、dockerのlogには、以下のようなメッセージが表示されていました。

こちらの原因はもう少し調査が必要になりそうです。

jupyterlab  | ERROR: received bad message: No such comm target registered: jupyter.widget.control
jupyterlab  | Message content: null
jupyterlab  | codon: /tmp/codon/jupyter/build/_deps/xtl-src/include/xtl/xbasic_fixed_string.hpp:109: void xtl::detail::fixed_small_string_storage_impl::set_size(std::size_t) [T = char[56]]: Assertion `sz < N && "setting a small size"' failed.

ライプニッツの公式による円周率の近似計算

前回同様、ライプニッツの公式による円周率の近似計算を行い、処理速度を計測したいと思います。

使用するコードは以下のようになります。

共通

def main():
    max_iter = 10 * 1000 * 1000
    start_time = time.time()
    # estimate pi
    hat_pi = Leibniz(max_iter)
    elapsed_time = time.time() - start_time
    err = math.fabs(hat_pi - math.pi) / math.pi
    output = str(timedelta(seconds=elapsed_time))

    printf(hat_pi, 'Estimated Pi: {:.15f}')
    printf(err,    'Relative Err: {:.15e}')
    print(output)

Codon版

import time
from datetime import timedelta
from python import math

@python
def printf(data, _format):
    print(_format.format(data))

def Leibniz(max_iter : int):
    arr = reversed(range(max_iter))
    _sum = 0.0

    for k in arr:
        _sum += ((-1) ** (k % 2) + 0.0) / (2 * k + 1)
    pi = _sum * 4

    return pi

python版

import time
from datetime import timedelta
import math

def printf(data, _format):
    print(_format.format(data))

def Leibniz(max_iter : int):
    arr = reversed(range(max_iter))
    _sum = 0.0

    for k in arr:
        _sum += ((-1) ** (k % 2) + 0.0) / (2 * k + 1)
    pi = _sum * 4

    return pi

計算結果

実行結果は以下のようになりました。

実行モード処理時間
python(デフォルト)6.238235
Codon0.138318
pythonで実行した場合
Codonで実行した場合

処理時間は、\(\frac{1}{45}\)倍位になっていますね。

ここから、Codonを利用することで、約45倍高速化で来ていることが分かります。

まとめ

今回は、Codon+JupyterLabの環境構築方法について解説しました。

手軽にPythonスクリプトを実行できるJupyterLabとPythonスクリプトを高速化できるCodonを利用することで、時間がかかるスクリプトを短時間で実行することが可能となります。

処理時間がボトルネックになっている方は、一度試してみてください。

効率良く技術習得したい方へ

今回の話の中で、プログラミングについてよく分からなかった方もいると思います。

このような場合、エラーが発生した際に対応できなくなってしまうため、経験者からフォローしてもらえる環境下で勉強することをおすすめします。

詳細は、以下の記事をご覧ください。

【比較】プログラミングスクールおすすめランキング6選【初心者向け】

続きを見る

スポンサードリンク



-プログラミング
-, , ,