"""環境変数による実行時設定の上書き(variant・Dr.Jit スレッド数).
同じシーン YAML を手元(Windows・llvm_ad_rgb)と研究室サーバ(cuda_ad_rgb)の両方で
使い回すため,YAML を書き換えずに実行環境側から設定を差し替える手段を提供する.
スイープスクリプトは sm-render をサブプロセスで呼ぶので,環境変数にしておけば
`sm-remote run` で 1 回指定するだけで配下の全レンダに伝わる.
- `SM_RENDER_VARIANT`: sm-render が使う Mitsuba variant(YAML の render.variant より優先)
- `SM_DRJIT_THREADS`: Dr.Jit(LLVM バックエンド / scalar レンダ)の CPU スレッド数上限.
共用サーバ(64 スレッド)で llvm_ad_rgb が全コアを占有するのを防ぐ
(2026-09-13 Newton で loadavg 33 → 87 を実測)
drjit は `apply_drjit_thread_count` の中で必要な時だけ import する
(未設定時は import しない=テスト・--help の軽量性を保つ).
"""
from __future__ import annotations
import logging
import os
from collections.abc import Mapping
_logger = logging.getLogger(__name__)
VARIANT_ENV_VAR = "SM_RENDER_VARIANT"
THREADS_ENV_VAR = "SM_DRJIT_THREADS"
# 同一プロセスで何度呼ばれても INFO ログは 1 回にする(runner と baker の両方から呼ばれるため)
_applied_thread_count: int | None = None
def resolve_render_variant(
yaml_variant: str,
cli_variant: str | None = None,
environ: Mapping[str, str] | None = None,
) -> str:
"""実際に使う Mitsuba variant を決める.
優先順位: CLI 引数 > 環境変数 `SM_RENDER_VARIANT` > YAML の render.variant.
空文字・空白のみの値は「指定なし」とみなす.
Args:
yaml_variant: シーン YAML の render.variant.
cli_variant: CLI の --variant(未指定なら None).
environ: 参照する環境変数(None なら os.environ).
Returns:
使用する variant 文字列.
"""
if cli_variant is not None and cli_variant.strip():
return cli_variant.strip()
env = os.environ if environ is None else environ
env_variant = env.get(VARIANT_ENV_VAR, "").strip()
if env_variant:
return env_variant
return yaml_variant
def resolve_thread_count(environ: Mapping[str, str] | None = None) -> int | None:
"""環境変数 `SM_DRJIT_THREADS` から Dr.Jit のスレッド数上限を読む.
Args:
environ: 参照する環境変数(None なら os.environ).
Returns:
スレッド数(1 以上).未設定・空なら None(Dr.Jit 既定=全論理コア).
Raises:
ValueError: 値が整数でない,または 1 未満の場合.
"""
env = os.environ if environ is None else environ
raw = env.get(THREADS_ENV_VAR, "").strip()
if not raw:
return None
try:
count = int(raw)
except ValueError as exc:
msg = f"環境変数 {THREADS_ENV_VAR}={raw!r} は整数ではありません(1 以上の整数を指定)."
raise ValueError(msg) from exc
if count < 1:
msg = f"環境変数 {THREADS_ENV_VAR}={count} は 1 以上である必要があります."
raise ValueError(msg)
return count
def apply_drjit_thread_count(environ: Mapping[str, str] | None = None) -> int | None:
"""`SM_DRJIT_THREADS` が設定されていれば Dr.Jit のスレッドプールをその数に制限する.
未設定なら何もしない(drjit も import しない).`dr.set_thread_count` は
JIT 初期化の前後どちらで呼んでもよく,冪等なので複数箇所から呼んでよい.
Args:
environ: 参照する環境変数(None なら os.environ).
Returns:
設定したスレッド数.未設定なら None.
Raises:
ValueError: 環境変数の値が不正な場合.
"""
global _applied_thread_count # noqa: PLW0603
count = resolve_thread_count(environ)
if count is None:
return None
import drjit as dr
dr.set_thread_count(count)
if _applied_thread_count != count:
_logger.info("Dr.Jit の CPU スレッド数を %d に制限しました(%s)", count, THREADS_ENV_VAR)
_applied_thread_count = count
return count