JuliaTokyo 11

業務に耐えうる Julia のエコシステムの整備

~Turning Dreams into Reality~

terasakisatoshi@AtelierArith

https://atelierarith.github.io/JuliaTokyoFeb2024/

自己紹介

  • @terasakisatoshi
  • AtelierArith は屋号
    • 個人の立場で話す際はこれを使っている
    • Julia・Python 周りのソフトウェア開発,相談役(自称)
      • 大学の研究者,企業,個人
    • アート活動
      • 仙台はアートの街でもあるのよ🎵

自分の Julia 言語歴

  • v0.6, v0.7 ぐらいの時から

    • 何回か挫折, Back to the Python の黒歴史
      • 若気の至りだったの... 許して
  • 勉強会(JuliaTokai), Qiita/Zenn でちまちま書いています

    • Python に戻ろうとすると上昇負荷を感じるようになった
      • あこがれ(早く速いソフトを作る)は止められねぇんだ

自分の Julia 言語歴 (直近)

Julia 言語の良いところ(1)

  • 数式と実装の乖離を抑える仕組み
julia> α # \alpha + TAB`

help?> Γᵢʲₖ
"Γᵢʲₖ" can be typed by \Gamma<tab>\_i<tab>\^j<tab>\_k<tab>

ライブラリを簡単に導入・利用ができる

julia> using Pkg; Pkg.add("Primes")
julia> using Primes; factor(2024)
2^3 * 11 * 23

Julia 言語の良いところ(2)

  • Python との相互連携
  • リアクティブノートブックのサポート(Pluto.jl)
  • パッケージ開発のエコシステムの充実
  • VS Code の上で環境構築

Julia 言語の良いところ(3)

  • コミュニティの温かさ(個人的な意見です)
    • バックグラウンドが多様
    • 計算機に関して正規の教育を受けてない自分からするとありがたい環境
      • 計算機を通して何か研究・学術的なことを楽しめる
      • コーディング・エンジニアリングを楽しめる
      • 互いの立場から他方を学べる機会が見えてくる

Julia 言語の課題? (1)

  • インストール Julia バージョンアップがだるい
    • juliaup を使いましょう
  • 入門書
    • あるでしょ?監修した
  • チュートリアル
  • インターネット上での入門記事
  • それなりの規模のパッケージ

Julia 言語の課題? (2)

  • バグが
    • 最近はすんなりパッケージが入る
    • コードは GitHub などで公開されてるからパッチを送れば良い
  • ライブラリ
    • 安定化してきたし,探したら出てくる
    • PythonCall.jl などで他言語連携が可能
    • OpenCV_jll などビルド済みバイナリ,ライブラリを利用することもできる
      • OpenCV は本日時点で Julia v1.10 だとトラブってるので v1.9 でお試しください.
    • ないならば,作ってしまえよ,ホトトギス
      • 口先だけでなく手を動かすのが大事

Julia 言語の課題? (3)

  • 専門的な内容
    • 出てるでしょ? 2023 年は豊作だった
    • 数値計算,機械学習,数理最適化,データサイエンス,Web 開発
      • ないならば,執筆チャンスよ?,ホトトギス
  • パッケージのロードが遅い
    • お使いの Julia は最新ですか?
    • 1.9 から TTFL/TTFX 問題が大幅に解決できた
    • Windows ユーザは WSL がいいかもしれない

Julia 言語の課題? (4)

  • どう開発するの?作ったパッケージをどう管理・運用してるの?
    • キュ,キュ〜ン (((´・ω・`)))
    • いい質問ですね
  • 確かに.エンジニアリングの部分は不足してるかも?

下記の疑問にどう対処するか?

  • 諸事情で GitHub ではないプラットフォームで運用したい
  • 研究段階のものなので Public にできない(公開できない)
  • コードが膨らんできた.複数のパッケージにしたい
    • PkgA.jl, PkgB.jl, PkgC.jl
    • PkgEntryPoint.jl を作りたい
  • 組織内で使ってもらうにはどうすればいいか?

ということで

そこらへんの領域をトークしようと思います

  • 埼玉大, LMU Munich, CEA Grenoble らのグループ(Tensors4Fields)
    の共同研究での Julia 開発基盤を整えたときの知見をいくつか紹介

以下想定していること(1)

  • Julia 導入済み
  • 作りたいものが決まっている
  • Git/GitHub/GitLab の操作を知っていること
$ julia # これはシェルで
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.10.0 (2023-12-25)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> # これは Julia の prompt

以下想定してること(2)

  • 何かのパッケージを作った.そして使ってほしいとする.
julia> using Pkg
julia> Pkg.add(url="https://github.com/AtelierArith/PkgBump.jl")

課題(仮定)

  • URL 直接入力したくない! Pkg.add("PkgBump") で入れたい
  • プライベートリポジトリで開発してるからコードを公開したくない
  • オレオレ用途が強いから General に登録するのは億劫

組織で Julia を活用する際の要請

  • 次のような要請は当然出てくる

    • 営利組織で開発したものなので公開したくない
    • 現在進行形で開発中当分時間がかかる
      • でも組織メンバーには使わせたい
      • とりあえず動く安定版を提供したい
  • あんまり難しいことを考えずに自然な開発体験が欲しい:

julia> using Pkg
julia> Pkg.add("MyPrivatePkg")
julia> using MyPrivatePkg: something
julia> something("like that")

Tensors4Fields における
Julia 開発・運用

Tensors4Fields における
Julia 開発・運用

???

Tensors4Fields の状況(過去形)

  • コードは GitLab で管理
    • 理由はあえて聞いていない
    • やったことないけれど多分できますの精神
  • コードの量は企業に負けない規模だと思う
    • 埼玉大,もっと自慢してもいいと思うよ!
  • 組織内レジストリが一個ある
    • 登録の仕方がわかってない状態
  • ここら辺から整備することに

Tensors4Fields の状況(現在形)

  • パッケージ開発エコシステムの整備(完了)
    • パッケージのテンプレート
    • プライベートレジストリに登録作業を簡便化
    • TagBot, CompatHelper などの自動化
  • Wiki の作成
  • 研究者はアルゴリズム・実装を頑張る
  • 自分は開発基盤の整備
    • 既存実装の高速化を担当
    • GitLab CI/CD の盆栽

Tensors4Fields で導入・整備したもの

  • OSS ベースのパッケージは GitHub 上での運用を前提として説明しがち...
  • GitLab でホストしてるのでエコシステムを自作(現在非公開)
    • .gitlab-ci.yml の整備

Tensors4Fields で導入・整備したもの

  • 使いまわせるものは使い回す(工数の削減)
    • RegistryCI.jl (OSS)
    • CompatHelper.jl (OSS)
  • 自動化できるものは自動化する
    • 定期実行スケジューラの導入
  • Wiki の整備

T4FTemplate.jl

  • Tensors4Fields の用途に合わせて PkgTemplates.jl のテンプレートを拡張
  • 例えばテストはこんな感じ
using Test
using Aqua, JET

@testset "MyJuliaPkg.jl" begin
    @testset "Code quality (Aqua.jl)" begin
        Aqua.test_all(MyJuliaPkg; ambiguities=false, deps_compat=false)
    end

    if VERSION >= v"1.9"
        @testset "Code linting (JET.jl)" begin
            JET.test_package(MyJuliaPkg; target_defined_modules=true)
        end
    end

    # Write your tests here.
end

T4FRegistrator.jl

  • Tensors4Fields グループ向けのレジストリに開発中のパッケージのメタデータ登録の煩雑さを軽減する
  • GitHub 上での @JuliaRegistrator register が担う機能を担当
  • 最近作った自作パッケージ AtelierArith/PkgBump.jl を組み込んで Project.toml の更新も自動化できるようになった. 🎶
julia> # パッチバージョンをインクレメント
julia> using T4FRegistrator; bumppatch()
julia> # CI がパスするまでしばらく待つ. git pull をする
julia> using T4FRegistrator; register()
julia> # CI がパスするまでしばらく待つ.

Registrator.jl なんもわからん

  • 蛇足ですがRegistrator.jl/Hosting Your Own の説明, セットアップ手順が宇宙猫なので誰か日本語で解説を書いてください.
    • 喜んで donation します.

T4FTagBot

  • 最初は不要だと思ってた
  • バージョンアップをする心理的障壁がなくなった
    • パッチアップデートが増えるようになった(良いこと)
  • アレェ!いつの間にか更新されてる
    • あのバージョンはどれだっけ?(タグ打ってないのでわからない)
      • TagBot 欲しくなってきた(やっぱ必要だった...)
  • GitLab 向けので直近でメンテされているものがなさそうだったので invenia/Ta
    gBotGitLab
    を解読して魔改造した
    • T4FTagBot を作った (非公開)

TagBot(GitLab) の仕組み

  • GitLab API に大きく依存. Python はその辺が充実してる
    • GitLab.jl は v3 で止まっている (今は v4 が主流?)
  • レジストリから当該パッケージの Package.tomlVersions.toml を取得
  • Versions.toml からどのタイミングで tag を打つのかを判断
    • GitLab API で tag を push
  • v0.1.0 と v0.1.1 の間にあったコードの差分,Merge Request, Issue の履歴を収集
    • GitLab の API で取得
  • リリースノートを作成
    • GitLab API で作成

リリースノートに関して

  • サボって git push origin main をすると変更がリリースノートに反映されない
  • 差分の理解,過去に戻ることができるのがバージョン管理のメリットだった
  • ブランチを作成してMR/PRを出してセルフマージすると後々楽になります

Scheduled pipelines

  • T4FTagBot は PAT を使って認証し利用できる
    • でも人間がするのは面倒
    • 定期的に実行すれば良い
  • Scheduled pipelines で定期実行する
    • もちろん手動でもできる

CompatHelper.jl

  • 度々 breaking change が入るのでマイナーバージョンをインクレメントが必要
# Project.toml
[compat]
Example="0.5" # <--- これを編集してくれる

下記を実行できる Job を作れば良い Custom-registries を参照

CompatHelper.main(;use_existing_registries=true)
  • これも Scheduled pipelines で実行する.
    • MR までは自動化. Merge は人間がする.

GitHub ユーザの皆さんは

workflow dispatch(GitHub の機能)

GitLab CI/CD の盆栽をすることで

  • GitLab の操作を覚えた(テッテレー)
  • グループ内のリポジトリのアップデート頻度が増えた
  • 自作ツールによるエコシステムの充実と定型操作の自動化を進めた
    • 研究者はアルゴリズムの実装にフォーカスができる
    • bumppatch(), register() の使い方さえ覚えてもらえれば良い
      • いい意味で怠けさせるようにできた(と思う).
  • 今後の課題:
    • GitLab Runner における CI の高速化
      • キャッシュの活用とか

まとめ

  • GitLab の上でプライベートリポジトリをどのように管理するかの知見を共有した
    • GitHub でするともう少し楽にできる
    • プライベートリポジトリ間の制御がいるのでアクセストークンの知見は必須
  • Git/リモートリポジトリのシステムでプライベートパッケージを管理する仕組みが整えるのが Julia の良さな気がする.
    • Python でやれって言われたらもう少しむずいと思う(やったことがない)
  • 自分が持っていた Julia や周辺のエンジニアリングスキルを発揮できたとおもう

以上

Appendix

使わない資料を置いておく

へー,便利やん

これ,研究室の学生にやらせればえんちゃうん?

と思うじゃん?

  • さらっと説明してるけれど色々省いている
    • ソフトウェア技術職で働いているならまだしも・・・
  • あえて説明してないがアクセストークンの権限はグループトークンのような強い権限が必要になるケースがある.
    • 組織の Owner(e.g., 研究室の運営責任者,指導教官)程度の責任をもって業務を遂行できる人をアサインするべき
    • エコシステムの整備は研究グループの生産性を支える重要なタスクです
      • 「基礎研究が重要である」ことを理解する皆さまならその重要性が理解できると思います

にゅ〜ん

  • 餅は餅屋のついたものがいちばん美味しいのです

Pkg.jl(素朴な作り方)

julia> using Pkg; Pkg.generate("MyPkg.jl")
  Generating  project MyPkg:
    MyPkg.jl/Project.toml
    MyPkg.jl/src/MyPkg.jl
Dict{SubString{String}, Base.UUID} with 1 entry:
  "MyPkg" => UUID("a7a7ec01-b8e6-4c36-ab78-2e58ad32e434")

julia>; # shell mode に遷移
shell> tree
.
└── MyPkg.jl
    ├── Project.toml
    └── src
        └── MyPkg.jl

3 directories, 2 files

ただし,必要最低限のものしか作られない.

PkgTemplates.jl

julia> using PkgTemplates: Template
julia> tpl = Template(; dir=pwd());
julia> tpl("MyPkg.jl")

Template オブジェクトは色々調整できる:

Template(; 
  user="<GitHub/GitLab の組織・ユーザ名>", 
  host="gitlab.com", # GitLab の場合
  dir=pwd(),
  plugins=[ProjectFile(; version=v"0.1.0"), Git()] 
)

PkgTemplates.jl の拡張

  • 野良パッケージ MyTemplate.jl のようにして自分の用途に拡張ができる.
  • GitLab の場合, 自分の組織に特化した .gitlab-ci.yml があれば GitLabCI(file="path/to/.gitlab-ci.yml") で指定可能
    • 変数は Mustache.jl によるテンプレートシステムで記述

パッケージの開発 (試行錯誤中)

  • Jupyter(IJulia.jl)
    • jupytext でペアリングしたファイルを管理
    • *ipynb.gitgnore に記述
  • Pluto.jl
    • .jl スクリプトとして管理
$ tree -d playground
playground
├── notebook # Jupyterのファイル
│   ├── julia # .jl
│   └── python # .py
└── pluto # Pluto.jl のファイル

6 directories

Pluto.jl の嬉しいところ

  • Python に依存しないので環境構築が楽
  • リアクティブ
    • 依存関係を検知し変更を反映
  • Ctrl-D/Cmd-D でマルチカーソル機能が使える
  • PlutoUI.jl による UI を作ることができる
  • 再現性の確保
    • Manifest.toml に相当する情報が入ってる

パッケージ開発 src/MyPkg.jl

# src/MyPkg.jl
module MyPkg

"""
    f(x)

実数 `x` に対して 1 を加える
"""
function f(x::Real)
	  x + one(x)
end

end # module

パッケージ開発(規模が大きくなる場合)

  • ファイルに分割して include 関数で機能を取り込むことができる
module MyPkg

using LinearAlgebra: dot

export azarashi, Goma

include("util.jl")
include("core.jl")
include("kyukkyu.jl") # Goma, azarashi が実装されている

end # module
  • sub module はあまり使ってない.多重ディスパッチで済む

Pkg.test() 単体テスト

# test/runtests.jl
using MyPkg.jl

@testset "" begin
    @test f(1) == 2
    @test f(3.0) == 4.0
    # f("Hi") は MethodError になることを確認したい
    @test_throws MethodError f("Hi")
end 
$ ls
Project.toml src test
$ julia --project -q
julia> using Pkg; Pkg.test()

ReTestItems.jl

  • 規模が大きくなるとすごい時間がかかる.
  • ReTestItems.jl
    • @testitem 単位で独立にテストができる
    • VS Code の上でポチッと押せる
# ReTestItems.jl のサンプルコード
@testitem "Do cool stuff" begin
    using MyPkgDep
    function really_cool_stuff()
        # ...
    end
    @testset "Cool stuff doing" begin
        @test really_cool_stuff()
    end
end

ReTestItems.jl

  • 嬉しいところ
    • 並列実行も可能
    • ReTestItems.runtests 関数に並列数指定可能
  • 環境変数を指定することもできる
$ RETESTITEMS_NWORKERS=8 julia --project -e 'using Pkg; Pkg.test()'

Registrator.jl

  • Julia 公式 General レジストリに登録すれば Pkg.add("PkgBump") で入る.
    • Registrator.jl からボットをインストールする
    • ソースは GitHub で管理, public リポジトリならこれでOK
  • 初めて当該パッケージを登録する場合は 3 日の待機期間があることに注意.
  • CI・テストが通らないと採用されない

組織開発のフロー

graph TD;
    Repo --> |CI/CD/MR/PR/merge| Repo
    TagBot -->|create tags| Repo[Repository]
    Repo --> |pull| Dev1[Developer1]
    Repo --> |pull| Dev2[Developer2]
    Dev1 -->|register| Registry
    Dev1 -->|push| Repo
    Dev2 -->|push| Repo
    ScheduledPipeline --> |run| CompatHelper
    ScheduledPipeline --> |run| TagBot
    CompatHelper[CompatHelper] -->|update Project.toml| Repo
    Registry --> |Pkg.Registry.add| User
    Repo --> |Pkg.add| User
    Registry --> |pull| TagBot
    Registry --> |pull| CompatHelper

Julia のレジストリについて

  • 登録されているパッケージの情報を格納する
    • リポジトリの名前, UUID, URL (Package.toml)
    • リリースバージョンと git-tree-sha1 の組 (Versions.toml)
    • Compat.toml, Deps.toml
$ tree -d
.
├── A
│   └── APkg
├── B
│   ├── BPkg
│   └── BPkg
└── T
    ├── T4FXXX
    └── T4FYYY

LocalRegistry.jl

  • レジストリの作成自体は LocalRegistry.jl を使えば良い
julia> using LocalRegistry: create_registry
julia> create_registry(
	"<レジストリ名>",
	"<リモートリポジトリのURL>",
	description="<このレジストリの説明>"
)

LocalRegistry.jl

  • なんだか難しそうだけれど所詮 Git のリポジトリ
  • 情報の登録はブランチの作成,コミット,プッシュをする方法を指示すれば良い
LocalRegistry.register(
    package_dir;
    registry="<リポジトリのURL>"
    branch=branch,
    commit=true,
    push=true,
    create_gitlab_mr=true, # GitLab のみ有効
)

RegistryCI.jl

  • 登録するをする Merge Request, Pull Request に対してレジストリの整合を確認するテストを実行できる.
  • Using RegistryCI on your own package registry に全て書いてある
    • とりあえず下記のコマンドを実行できれば良い
julia --project=.ci/ --color=yes -e 'import RegistryCI; RegistryCI.test(registry_deps=["General"])'

Pkg.Registry.add

julia> using Pkg; Pkg.Registry.add(RegistrySpec(url="<組織のレジストリ>"))

これでレジストリに登録されたパッケージは Pkg.add(["PkgName"]) で導入が可能

end Appendix