plan チュートリアル

このチュートリアルでは、データ並列操作を Plan として記述し、v1 のサブセットに照らして検査し、プランが拒否されたときに理由を読む方法を示します。プランは単なるデータなので、このパッケージはすべてのターゲットで使えます。

クイックスタート

パッケージと、ポリシーのための shared をインポートします。

import {
  "Luna-Flow/luna_thread/plan",
  "Luna-Flow/luna_thread/shared",
}

8 個の Int 値に対するマッププランを作って検証します。

test "plan quick start" {
  let plan = @plan.map("double", @plan.i32_type(), 8)
  inspect(@plan.is_runnable(plan), content="true")
  inspect(plan.label(), content="double")
}

日常的なタスク

プランにポリシーを与える

既定のポリシーはワーカー 1、チャンクサイズ 1 です。make_execution_policy で並列のポリシーを作り、ビルダーに渡します。

test "policy" {
  let policy = @shared.make_execution_policy(worker_count=4, chunk_size=16).unwrap()
  let plan = @plan.map_reduce("sum", @plan.i64_type(), 64, @plan.sum_reduction(), policy~)
  assert_eq(plan.policy().worker_count, 4)
  assert_true(@plan.has_sum_reduction(plan))
  assert_true(@plan.is_runnable(plan))
}

プランが拒否された理由を読む

validate はすべての問題を一度に返すので、まとめて報告できます。

test "issues" {
  let policy = @shared.make_execution_policy(worker_count=8, chunk_size=8).unwrap()
  let plan = @plan.reduce("tiny", @plan.f32_type(), 4, @plan.custom_reduction("xor"), policy~)
  let issues = @plan.validate(plan)
  assert_true(issues.any(@plan.is_unsupported_value_type))
  assert_true(issues.any(@plan.is_chunk_size_too_large))
  assert_true(issues.any(@plan.is_unsupported_reduction_kernel))
  assert_eq(issues.length(), 4)
}

4 つ目の問題は WorkerCountExceedsInput です。要素 4 つに対してワーカーが 8 つあります。

珍しいプランを手で作る

Plan::new は渡されたものを何でも保存するので、スキャンの順序の要件のような検証規則をテストできます。

test "relaxed scan" {
  let plan = @plan.Plan::new(
    "prefix",
    @plan.scan_plan_kind(),
    @plan.DataDomain::new(@plan.i32_type(), 8),
    ordering=@shared.relaxed_order(),
  )
  let issues = @plan.validate(plan)
  assert_true(issues.any(@plan.is_unsupported_ordering))
  assert_true(issues.any(@plan.is_scan_requires_stable_ordering))
}

プランを調べる

アクセサは各フィールドを返し、種類の述語を使えばパターンマッチが要りません。

test "inspect" {
  let plan = @plan.scan("prefix", @plan.i64_type(), 32)
  assert_true(@plan.is_scan(plan))
  assert_eq(plan.reduction(), None)
  assert_true(plan.ordering() is @shared.PreserveInputOrder)
  assert_eq(plan.domain().input_length(), 32)
}

さらに進んで

プランは backend/native を通じて実行可能になります。その map_request_from_plan、reduce_request_from_plan、scan_request_from_plan は有効なプランを型付きのリクエストに変換します。また、ワークフローでは @workflow.compute_node がプランをタスクグラフに埋め込み、@workflow.validate がその問題を InvalidComputePlan として報告します。ライブラリのコードはファサードではなく Plan と ValidationIssue に対して書けば、すべてのターゲットでビルドできます。

よくある落とし穴

  • is_runnable は、ネイティブカーネルが要求する「チャンクごとに 1 つのワーカー」(w≥⌈n/c⌉w \ge \lceil n / c \rceil)を検査しません。
  • reduce と scan は常に入力順序を保ちます。ほかの順序を試すには Plan::new を使ってください。
  • スキャンプランにはカーネルがなく、ネイティブのスキャンは常に累積和です。
  • 列挙型はパッケージの外では読み取り専用です。値を作るときは @plan.I32 ではなく @plan.i32_type() と書いてください。@plan.I32 でのパターンマッチは使えます。

次のステップ

plan API に validate のすべての規則が載っています。plan の設計では、各種類のプランが何を計算するか、スキャンに順序付きの入力が必要な理由を説明しています。workflow チュートリアルではプランをタスクグラフに組み込みます。