0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RamaLamaの`--dryrun`が`--backend`オプションを反映しないバグを調べた

0
Posted at

はじめに

前回の記事で、RamaLamaのコンテナイメージ自動選択の仕組みを調べました。その中で、--backend rocmを指定しても--dryrunの出力に反映されないことに気づいたので、原因をソースコードから追ってみました。

環境

  • OS: Ubuntu 24.04
  • GPU: AMD Radeon 890M(iGPU)
  • RamaLama: 0.24.0
  • Podman: 4.9.3

再現手順

デフォルト(Vulkan)

$ ramalama --dryrun run gpt-oss
podman run ... quay.io/ramalama/ramalama:0.24 llama-server ...

AMD GPUではデフォルトでVulkanバックエンドが選択され、ベースイメージramalama:0.24が使われます。

--backend rocmを指定

$ ramalama --dryrun run gpt-oss --backend rocm
podman run ... quay.io/ramalama/ramalama:0.24 llama-server ...

--backend rocmを指定したのに、イメージはramalama:0.24のまま。rocm:0.24にならない。

--backend rocm + --imageを明示指定

$ ramalama --dryrun run gpt-oss --backend rocm --image quay.io/ramalama/rocm:0.24
podman run ... quay.io/ramalama/rocm:0.24 llama-server ...

--imageで明示すればROCmイメージになるが、--backendだけでは切り替わらない。

期待される動作

--backend rocmを指定したら、--dryrunの出力でもイメージがquay.io/ramalama/rocm:0.24になるべき。

原因

args.imageの値が決まるタイミングが2段階あり、--dryrunでは2段階目がスキップされることが原因です。

第1段階: argparseのデフォルト値(--dryrun/通常run共通)

cli.py L54-56:

@lru_cache(maxsize=1)
def default_image() -> str:
    return accel_image(ActiveConfig())

cli.py L857-862:

parser.add_argument(
    "--image",
    default=default_image(),
    help="OCI container image to run with the specified AI model",
    action=OverrideDefaultAction,
    completer=local_images,
)

--imageのデフォルト値はdefault_image()で決まります。この関数はaccel_image()を呼び、その中でget_container_image(config, gpu_type)が実行されます。

ここがポイントで、argparseのデフォルト値は引数解析前に評価されるため、この時点では--backend rocmがconfigに反映されていません。get_container_image()内のbackendは"auto"のままです。

llama_cpp.py L335-346:

def get_container_image(self, config: Any, detected_gpu_type: str) -> Optional[str]:
    rt_config = self.get_runtime_config(config)
    backend = rt_config.backend  # ← この時点では "auto"
    if backend == "auto":
        preferences = get_gpu_backend_preferences(detected_gpu_type)
        if preferences:
            gpu_type = backend_to_gpu_env(preferences[0])  # → "vulkan" → "GGML_VK_VISIBLE_DEVICES"
    ...

AMD GPUの場合preferences[0]は"vulkan"なので、gpu_typeは"GGML_VK_VISIBLE_DEVICES"になります。このキーは_LLAMA_CPP_IMAGESに存在しないため、config.default_image(= ramalama:0.24)にフォールバックします。

第2段階: 実行時の上書き(通常runのみ)

common.py L142-148:

def _do_run(self, args, model):
    ...
    if args.container and not args.dryrun:  # ← dryrunだとスキップ!
        config = ActiveConfig()
        should_pull = config.pull in ["always", "missing", "newer"]
        args.image = ensure_image(
            config.engine, accel_image(config), should_pull=should_pull, ...
        )

通常のrunでは、--backend rocmがconfigに反映された後にaccel_image(config)が再度呼ばれ、正しいイメージ(rocm:0.24)でargs.imageが上書きされます。

しかし**--dryrunではこのブロック全体がnot args.dryrunでスキップされる**ため、第1段階のデフォルト値(ramalama:0.24)がそのまま使われます。

フロー図

[argparse] --image のデフォルト値を評価
  └→ default_image() → accel_image() → get_container_image()
       backend = "auto"(--backend rocmはまだ未反映)
       AMD GPU → preferences[0] = "vulkan"
       → args.image = "ramalama:0.24"

[argparse] コマンドライン引数を解析
  └→ --backend rocm が config に反映される

[_do_run]
  ├─ 通常run: args.image = accel_image(config)  ← backendが反映済み → "rocm:0.24" ✓
  └─ --dryrun: スキップ → args.image は "ramalama:0.24" のまま ✗

実際の影響

--dryrunは「実際に実行されるコマンドを事前確認する」ためのオプションです。しかし、このバグにより--backend rocmを指定しても出力されるイメージ名がデフォルトのままになるため、dryrunの出力が実際の実行内容と一致しないという問題があります。

通常のrunでは、_do_run内でaccel_image(config)が再度呼ばれ、--backend rocmが反映された状態でイメージ解決が行われるため、正しくrocm:0.24が選択されます。つまり**--dryrunの出力だけが実際の動作と食い違う**状態です。

実行前に--dryrunでコマンドを確認し、そのままコピーして手動実行するようなワークフローでは、間違ったイメージで起動してしまう可能性があります。

修正案

_do_runの--dryrunでもイメージ解決を行うようにすれば、--dryrun出力の不整合は解消できます。

def _do_run(self, args, model):
    ...
    if args.container:
        config = ActiveConfig()
        should_pull = config.pull in ["always", "missing", "newer"]
        if args.dryrun:
            args.image = accel_image(config)  # pullせずにイメージ名だけ解決
        else:
            args.image = ensure_image(
                config.engine, accel_image(config), should_pull=should_pull, ...
            )

v0.25での修正

この問題は、v0.25でPR #2932に含まれるコミット713cd813で修正されていました。

修正のアプローチは、本記事で分析した「第2段階(_do_run内)でdryrunでもイメージ解決する」という方法ではなく、parse_args_from_cmdの中で、引数解析の完了後にargs.imageを再評価するというものです。

修正内容

cli.py L251-254:

# The runtime config now carries --backend, so the image the command will
# run can be resolved for real.
if hasattr(args, "image") and not getattr(args, "image_override", False):
    args.image = accel_image(config)

parse_args_from_cmdは引数解析を2段階で行います。

  1. Phase 1: get_initial_parser()で--backendなどのグローバルオプションを先行解析し、configに反映
  2. Phase 2: get_parser()でサブコマンドを含む全引数を解析

問題は、Phase 2で--imageのデフォルト値(default_image())が評価される時点では、--backendがconfigに反映済みのように見えて、実はdefault_image()が@lru_cacheでキャッシュされているため、最初の呼び出し時の値がそのまま使われる点です。

この修正では、Phase 2の引数解析とランタイムプラグインへのconfig同期(sync_args_to_runtime_config)が完了した後に、--imageがユーザーから明示指定されていなければ(image_overrideがFalseなら)、accel_image(config)で再評価しています。

さらに、OverrideDefaultActionクラスを拡張して、argparseのデフォルト値とユーザーが明示的に渡した値を区別できるようにしています。

class OverrideDefaultAction(argparse.Action):
    dests: set[str] = set()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        OverrideDefaultAction.dests.add(self.dest)

    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)
        setattr(namespace, self.dest + '_override', True)

これにより、--imageを指定しない場合はimage_overrideがFalseのまま → 再評価される、--imageを指定した場合はimage_overrideがTrue → ユーザーの指定が尊重される、という動作になります。

フロー図(修正後)

[Phase 1] get_initial_parser() で --backend rocm を先行解析
  └→ config.backend = "rocm"

[Phase 2] get_parser() で全引数を解析
  └→ default_image()はキャッシュ済み → args.image = "ramalama:0.25"(古い値)

[sync_args_to_runtime_config] ランタイムconfigに--backendを同期

[再評価] image_override == False なので accel_image(config) を再呼び出し
  └→ config.backend = "rocm" が反映済み → args.image = "rocm:0.25" ✓

本記事で提案した_do_run内での修正案は、--dryrunに限定した対症療法でしたが、実際のupstreamの修正はparse_args_from_cmdで引数解析直後に解決するため、--dryrunだけでなく--generateやその他のコードパスも含めて一括で正しいイメージが設定されます。

まとめ

  • --dryrunは--backendオプションをイメージ選択に反映しない(v0.24時点)
  • 原因: argparseのデフォルト値が--backendの解析前に評価され、--dryrunでは実行時の上書きがスキップされるため
  • 通常のrunでは正しいイメージが選択されるが、--dryrunの出力だけが実際の動作と一致しない
  • v0.25で713cd813により修正済み: parse_args_from_cmd内で引数解析完了後にargs.imageを再評価する方式
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?