---
title: lora
---

<div class="python-reference">
<a id="fastvideo.train.utils.lora-classes"></a>
<a id="fastvideo.train.utils.lora-functions"></a>
<a id="fastvideo.train.utils.lora.LoraConfig-functions"></a>
<a id="fastvideo.train.utils.lora"></a>
<p>Training-side LoRA utilities for <code>fastvideo.train</code> model plugins.</p>
<h2 id="fastvideo.train.utils.lora.get_local_torch_device"><code>fastvideo.train.utils.lora.get_local_torch_device</code></h2>
<pre class="doc-signature"><code>get_local_torch_device() -&gt; torch.device</code></pre>
<p>Return the torch device for the current rank.</p>
<h2 id="fastvideo.train.utils.lora.init_logger"><code>fastvideo.train.utils.lora.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.train.utils.lora.LoraConfig"><code>fastvideo.train.utils.lora.LoraConfig</code></h2>
<pre class="doc-signature"><code>LoraConfig(enable: bool = False, rank: int | None = None, alpha: int | None = None, target_modules: list&#91;str&#93; | None = None)</code></pre>
<p>Structured LoRA settings for one <code>fastvideo.train</code> model role.</p>
<p>Parsed from the nested <code>models.&lt;role&gt;.lora</code> YAML block::</p>
<pre><code>lora:
  enable: true                       # default false
  rank: 16
  alpha: 32                          # defaults to rank when omitted
  target_modules: [to_q, to_k, to_v, to_out]
</code></pre>
<p><code>enable</code> is an explicit on/off switch so a config states its intent
plainly: the presence of <code>rank</code> alone never silently flips a run into
LoRA-only training.  When <code>enable</code> is false a still-present <code>rank</code> is
ignored (with an INFO log), so a configured-but-off block is valid.</p>

<details><summary>Source</summary><pre><code>@dataclass
class LoraConfig:
    &quot;&quot;&quot;Structured LoRA settings for one ``fastvideo.train`` model role.

    Parsed from the nested ``models.&lt;role&gt;.lora`` YAML block::

        lora:
          enable: true                       # default false
          rank: 16
          alpha: 32                          # defaults to rank when omitted
          target_modules: &#91;to_q, to_k, to_v, to_out&#93;

    ``enable`` is an explicit on/off switch so a config states its intent
    plainly: the presence of ``rank`` alone never silently flips a run into
    LoRA-only training.  When ``enable`` is false a still-present ``rank`` is
    ignored (with an INFO log), so a configured-but-off block is valid.
    &quot;&quot;&quot;

    enable: bool = False
    rank: int | None = None
    alpha: int | None = None
    target_modules: list&#91;str&#93; | None = None

    def __post_init__(self) -&gt; None:
        if self.rank is not None:
            self.rank = int(self.rank)
        if self.alpha is not None:
            self.alpha = int(self.alpha)
        if self.target_modules is not None:
            self.target_modules = list(self.target_modules)

        if self.enable:
            if self.rank is None:
                raise ValueError(&quot;models.&lt;role&gt;.lora.enable is true but lora.rank is unset &quot;
                                 &quot;— an explicit positive rank is required to enable LoRA&quot;)
            if self.rank &lt;= 0:
                raise ValueError(f&quot;models.&lt;role&gt;.lora.rank must be &gt; 0, got &#123;self.rank!r&#125;&quot;)
        elif self.rank is not None:
            logger.info(
                &quot;models.&lt;role&gt;.lora.rank=%s is set but lora.enable is false — &quot;
                &quot;LoRA will NOT be applied (model trains on its normal &quot;
                &quot;trainable path).&quot;, self.rank)

    @classmethod
    def coerce(
        cls,
        obj: LoraConfig | dict&#91;str, Any&#93; | None,
    ) -&gt; LoraConfig | None:
        &quot;&quot;&quot;Normalize a raw YAML mapping (or existing config) into a LoraConfig.

        Returns ``None`` when no ``lora`` block was given, which callers treat
        as &quot;LoRA not configured&quot; — identical in effect to ``enable: false``.
        &quot;&quot;&quot;
        if obj is None:
            return None
        if isinstance(obj, LoraConfig):
            return obj
        if not isinstance(obj, dict):
            raise TypeError(&quot;models.&lt;role&gt;.lora must be a mapping or LoraConfig, got &quot;
                            f&quot;&#123;type(obj).__name__&#125;&quot;)
        unknown = set(obj) - set(_LORA_CONFIG_KEYS)
        if unknown:
            logger.warning(&quot;LoraConfig: ignoring unrecognized lora keys %s &quot;
                           &quot;(valid keys: %s)&quot;, sorted(unknown), list(_LORA_CONFIG_KEYS))
        return cls(
            enable=bool(obj.get(&quot;enable&quot;, False)),
            rank=obj.get(&quot;rank&quot;),
            alpha=obj.get(&quot;alpha&quot;),
            target_modules=obj.get(&quot;target_modules&quot;),
        )</code></pre></details>
<h3 id="fastvideo.train.utils.lora.LoraConfig.coerce"><code>fastvideo.train.utils.lora.LoraConfig.coerce</code></h3>
<pre class="doc-signature"><code>coerce(obj: LoraConfig | dict&#91;str, Any&#93; | None) -&gt; LoraConfig | None</code></pre>
<p>Normalize a raw YAML mapping (or existing config) into a LoraConfig.</p>
<p>Returns <code>None</code> when no <code>lora</code> block was given, which callers treat
as "LoRA not configured" — identical in effect to <code>enable: false</code>.</p>
<details><summary>Source</summary><pre><code>@classmethod
def coerce(
    cls,
    obj: LoraConfig | dict&#91;str, Any&#93; | None,
) -&gt; LoraConfig | None:
    &quot;&quot;&quot;Normalize a raw YAML mapping (or existing config) into a LoraConfig.

    Returns ``None`` when no ``lora`` block was given, which callers treat
    as &quot;LoRA not configured&quot; — identical in effect to ``enable: false``.
    &quot;&quot;&quot;
    if obj is None:
        return None
    if isinstance(obj, LoraConfig):
        return obj
    if not isinstance(obj, dict):
        raise TypeError(&quot;models.&lt;role&gt;.lora must be a mapping or LoraConfig, got &quot;
                        f&quot;&#123;type(obj).__name__&#125;&quot;)
    unknown = set(obj) - set(_LORA_CONFIG_KEYS)
    if unknown:
        logger.warning(&quot;LoraConfig: ignoring unrecognized lora keys %s &quot;
                       &quot;(valid keys: %s)&quot;, sorted(unknown), list(_LORA_CONFIG_KEYS))
    return cls(
        enable=bool(obj.get(&quot;enable&quot;, False)),
        rank=obj.get(&quot;rank&quot;),
        alpha=obj.get(&quot;alpha&quot;),
        target_modules=obj.get(&quot;target_modules&quot;),
    )</code></pre></details>
<h2 id="fastvideo.train.utils.lora.sync_replicated_lora_gradients"><code>fastvideo.train.utils.lora.sync_replicated_lora_gradients</code></h2>
<pre class="doc-signature"><code>sync_replicated_lora_gradients(transformer: torch.nn.Module) -&gt; int</code></pre>
<p>Average every replicated LoRA gradient over its mesh, on every rank.</p>
<p>A per-parameter <code>register_post_accumulate_grad_hook</code> is not enough: the
hook only fires on ranks where that parameter actually received a
gradient, so a rank whose loss skipped a layer issues fewer collectives
than its peers -- the ranks then pair the wrong tensors together, or hang
outright when the totals differ. This pass runs after backward and before
clipping/the optimizer step, walks the LoRA parameters in a stable order,
and joins the collective on every rank, zero-filling the gradient where the
rank contributed none (a rank that skipped the layer contributes exactly
zero to the average).</p>
<p>Returns the number of parameters synchronized.</p>
<details><summary>Source</summary><pre><code>def sync_replicated_lora_gradients(transformer: torch.nn.Module) -&gt; int:
    &quot;&quot;&quot;Average every replicated LoRA gradient over its mesh, on every rank.

    A per-parameter ``register_post_accumulate_grad_hook`` is not enough: the
    hook only fires on ranks where that parameter actually received a
    gradient, so a rank whose loss skipped a layer issues fewer collectives
    than its peers -- the ranks then pair the wrong tensors together, or hang
    outright when the totals differ. This pass runs after backward and before
    clipping/the optimizer step, walks the LoRA parameters in a stable order,
    and joins the collective on every rank, zero-filling the gradient where the
    rank contributed none (a rank that skipped the layer contributes exactly
    zero to the average).

    Returns the number of parameters synchronized.
    &quot;&quot;&quot;

    if not dist.is_available() or not dist.is_initialized():
        return 0

    synced = 0
    for module in transformer.modules():
        for attr_name in (&quot;lora_A&quot;, &quot;lora_B&quot;):
            parameter = getattr(module, attr_name, None)
            if not isinstance(parameter, DTensor) or not parameter.requires_grad:
                continue
            replicated_dims = _replicated_dims(parameter)
            if not replicated_dims:
                continue
            grad = parameter.grad
            if grad is None:
                local_grad = torch.zeros_like(parameter.to_local())
                parameter.grad = DTensor.from_local(
                    local_grad,
                    device_mesh=parameter.device_mesh,
                    placements=parameter.placements,
                    run_check=False,
                )
            else:
                local_grad = grad.to_local() if isinstance(grad, DTensor) else grad
            for mesh_dim in replicated_dims:
                dist.all_reduce(
                    local_grad,
                    group=parameter.device_mesh.get_group(mesh_dim),
                )
                local_grad.div_(parameter.device_mesh.size(mesh_dim))
            synced += 1
    return synced</code></pre></details>
<h2 id="fastvideo.train.utils.lora.finalize_lora_training"><code>fastvideo.train.utils.lora.finalize_lora_training</code></h2>
<pre class="doc-signature"><code>finalize_lora_training(transformer: torch.nn.Module) -&gt; int</code></pre>
<p>Freeze base weights and enable only adapter parameters after loading.</p>
<details><summary>Source</summary><pre><code>def finalize_lora_training(transformer: torch.nn.Module) -&gt; int:
    &quot;&quot;&quot;Freeze base weights and enable only adapter parameters after loading.&quot;&quot;&quot;
    transformer.requires_grad_(False)
    count = 0
    for module in transformer.modules():
        if not isinstance(module, BaseLayerWithLoRA):
            continue
        if module.lora_A is None or module.lora_B is None:
            raise RuntimeError(&quot;Training LoRA wrapper is missing adapter parameters&quot;)
        module.base_layer.requires_grad_(False)
        module.lora_A.requires_grad_(True)
        module.lora_B.requires_grad_(True)
        count += 1
    if count == 0:
        raise ValueError(&quot;No training LoRA wrappers were found after checkpoint loading&quot;)
    transformer.train()
    return count</code></pre></details>
<h2 id="fastvideo.train.utils.lora.enable_lora_training"><code>fastvideo.train.utils.lora.enable_lora_training</code></h2>
<pre class="doc-signature"><code>enable_lora_training(transformer: torch.nn.Module, *, lora_rank: int, lora_alpha: int | None = None, lora_target_modules: Sequence&#91;str&#93; | None = None, prepare_for_fsdp: bool = False, initialization_seed: int = 0) -&gt; int</code></pre>
<p>Replace supported linear layers with trainable LoRA wrappers.</p>
<p>Returns the number of layers converted to LoRA.</p>
<details><summary>Source</summary><pre><code>def enable_lora_training(
    transformer: torch.nn.Module,
    *,
    lora_rank: int,
    lora_alpha: int | None = None,
    lora_target_modules: Sequence&#91;str&#93; | None = None,
    prepare_for_fsdp: bool = False,
    initialization_seed: int = 0,
) -&gt; int:
    &quot;&quot;&quot;Replace supported linear layers with trainable LoRA wrappers.

    Returns the number of layers converted to LoRA.
    &quot;&quot;&quot;

    rank = int(lora_rank)
    if rank &lt;= 0:
        raise ValueError(f&quot;lora_rank must be &gt; 0, got &#123;lora_rank!r&#125;&quot;)

    alpha = int(lora_alpha) if lora_alpha is not None else rank
    target_modules = list(lora_target_modules or DEFAULT_LORA_TARGET_MODULES)
    arch_config = getattr(
        getattr(transformer, &quot;config&quot;, None),
        &quot;arch_config&quot;,
        None,
    )
    excluded_modules = list(getattr(arch_config, &quot;exclude_lora_layers&quot;, &#91;&#93;), )

    transformer.requires_grad_(False)

    replacements: list&#91;tuple&#91;str, BaseLayerWithLoRA&#93;&#93; = &#91;&#93;
    checkpoint_key_aliases: dict&#91;str, str&#93; = &#123;&#125;
    lora_parameter_names: set&#91;str&#93; = set()
    for module_name, module in transformer.named_modules():
        if not module_name:
            continue
        if not _is_target_layer(module_name, target_modules):
            continue
        if _is_excluded_layer(module_name, excluded_modules):
            continue

        lora_layer = get_lora_layer(
            module,
            lora_rank=rank,
            lora_alpha=alpha,
            training_mode=True,
        )
        if lora_layer is None:
            continue
        replacements.append((module_name, lora_layer))
        for state_name in module.state_dict():
            checkpoint_key_aliases&#91;f&quot;&#123;module_name&#125;.&#123;state_name&#125;&quot;&#93; = f&quot;&#123;module_name&#125;.base_layer.&#123;state_name&#125;&quot;
        lora_parameter_names.update(&#123;f&quot;&#123;module_name&#125;.lora_A&quot;, f&quot;&#123;module_name&#125;.lora_B&quot;&#125;)

    if not replacements:
        raise ValueError(&quot;No LoRA-compatible layers were found for the requested &quot;
                         f&quot;target modules: &#123;target_modules&#125;&quot;)

    for module_name, lora_layer in replacements:
        replace_submodule(transformer, module_name, lora_layer)

    if prepare_for_fsdp:
        # The FSDP loader consumes these aliases after the normal HF-to-native
        # mapping, then initializes adapter tensors that are intentionally absent
        # from the base checkpoint.  FSDP therefore owns base and adapter params
        # in the same parameter groups from the outset.
        setattr(transformer, LORA_CHECKPOINT_KEY_ALIASES_ATTR, checkpoint_key_aliases)
        setattr(
            transformer,
            LORA_MISSING_PARAMETER_INITIALIZER_ATTR,
            partial(
                _initialize_lora_parameter,
                parameter_names=frozenset(lora_parameter_names),
                seed=int(initialization_seed),
            ),
        )
    else:
        # Retain the legacy post-load path for existing model plugins.  New
        # distributed integrations must use ``prepare_for_fsdp=True``.
        _replicate_lora_parameters(transformer)
        finalize_lora_training(transformer)

    logger.info(
        &quot;Enabled LoRA training with rank=%d alpha=%d on %d layers&quot;,
        rank,
        alpha,
        len(replacements),
    )
    return len(replacements)</code></pre></details>
</div>
