Signed-off-by: HonestDeng <2958906959@qq.com> Signed-off-by: iwzbi <iwzbi@zju.edu.cn> Signed-off-by: iwzbi <wzbi@zju.edu.cn> Signed-off-by: Divyansh Singhvi <divyanshsinghvi@gmail.com> Signed-off-by: gcanlin <canlinguosdu@gmail.com> Signed-off-by: yinpeiqi <yinpeiqi809@gmail.com> Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com> Signed-off-by: 汪志鹏 <wangzhipeng628@gmail.com> Co-authored-by: catcat <108673086+iwzbi@users.noreply.github.com> Co-authored-by: iwzbi <iwzbi@zju.edu.cn> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: dsinghvi <divyanshsinghvi@gmail.com> Co-authored-by: Canlin Guo <canlinguosdu@gmail.com> Co-authored-by: Peiqi Yin <60515999+yinpeiqi@users.noreply.github.com> Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com>
138 lines
5.2 KiB
Python
138 lines
5.2 KiB
Python
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# This file has been modified by ByteDance Ltd. on 2025-09-30.
|
|
#
|
|
# Original file was released under Apache-2.0, with the full license text
|
|
# available at https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# This modified file is released under the same license.
|
|
#
|
|
# --- Upstream header preserved below ---
|
|
#
|
|
# Copyright 2024 Stability AI, Katherine Crowson and The HuggingFace Team. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import numpy as np
|
|
import torch
|
|
|
|
|
|
@dataclass
|
|
class FlowMatchEulerDiscreteSchedulerOutput:
|
|
prev_sample: torch.FloatTensor
|
|
|
|
|
|
class FlowMatchEulerDiscreteScheduler:
|
|
order = 1
|
|
|
|
def __init__(self, num_train_timesteps: int = 1000, dynamic_time_shift: bool = True):
|
|
self.num_train_timesteps = int(num_train_timesteps)
|
|
self.dynamic_time_shift = bool(dynamic_time_shift)
|
|
|
|
timesteps = torch.linspace(0, 1, self.num_train_timesteps + 1, dtype=torch.float32)[:-1]
|
|
self.timesteps = timesteps
|
|
self._timesteps = torch.cat([timesteps, torch.ones(1, dtype=timesteps.dtype)])
|
|
|
|
self._step_index: int | None = None
|
|
self._begin_index: int | None = None
|
|
|
|
@property
|
|
def step_index(self) -> int | None:
|
|
return self._step_index
|
|
|
|
@property
|
|
def begin_index(self) -> int | None:
|
|
return self._begin_index
|
|
|
|
def set_begin_index(self, begin_index: int = 0) -> None:
|
|
self._begin_index = int(begin_index)
|
|
|
|
def index_for_timestep(self, timestep: torch.Tensor, schedule_timesteps: torch.Tensor | None = None) -> int:
|
|
schedule_timesteps = self._timesteps if schedule_timesteps is None else schedule_timesteps
|
|
indices = (schedule_timesteps == timestep).nonzero()
|
|
pos = 1 if len(indices) > 1 else 0
|
|
return int(indices[pos].item())
|
|
|
|
def set_timesteps(
|
|
self,
|
|
num_inference_steps: int | None = None,
|
|
device: str | torch.device | None = None,
|
|
timesteps: list[float] | None = None,
|
|
num_tokens: int | None = None,
|
|
) -> None:
|
|
if timesteps is None:
|
|
if num_inference_steps is None:
|
|
raise ValueError("`num_inference_steps` must be provided when `timesteps` is None.")
|
|
timesteps_np = np.linspace(0, 1, num_inference_steps + 1, dtype=np.float32)[:-1]
|
|
if self.dynamic_time_shift and num_tokens is not None:
|
|
m = np.sqrt(float(num_tokens)) / 40.0
|
|
timesteps_np = timesteps_np / (m - m * timesteps_np + timesteps_np)
|
|
else:
|
|
timesteps_np = np.asarray(timesteps, dtype=np.float32)
|
|
|
|
timesteps_t = torch.from_numpy(timesteps_np).to(dtype=torch.float32, device=device)
|
|
self.timesteps = timesteps_t
|
|
self._timesteps = torch.cat([timesteps_t, torch.ones(1, device=timesteps_t.device, dtype=timesteps_t.dtype)])
|
|
|
|
self._step_index = None
|
|
self._begin_index = None
|
|
|
|
def _init_step_index(self, timestep: float | torch.Tensor) -> None:
|
|
if self.begin_index is None:
|
|
if isinstance(timestep, torch.Tensor):
|
|
timestep = timestep.to(self.timesteps.device)
|
|
else:
|
|
timestep = torch.tensor(timestep, device=self.timesteps.device, dtype=self.timesteps.dtype)
|
|
self._step_index = self.index_for_timestep(timestep)
|
|
else:
|
|
self._step_index = self._begin_index
|
|
|
|
def step(
|
|
self,
|
|
model_output: torch.FloatTensor,
|
|
timestep: float | torch.FloatTensor,
|
|
sample: torch.FloatTensor,
|
|
generator: torch.Generator | None = None, # noqa: ARG002 - kept for API compatibility
|
|
return_dict: bool = True,
|
|
) -> FlowMatchEulerDiscreteSchedulerOutput | tuple[torch.FloatTensor]:
|
|
if isinstance(timestep, (int, torch.IntTensor, torch.LongTensor)):
|
|
raise ValueError(
|
|
"Integer timesteps (e.g. a loop index from enumerate) are not supported; "
|
|
"pass a value from `scheduler.timesteps` instead.",
|
|
)
|
|
|
|
if self.step_index is None:
|
|
self._init_step_index(timestep)
|
|
assert self._step_index is not None
|
|
|
|
sample_fp32 = sample.to(torch.float32)
|
|
t = self._timesteps[self._step_index]
|
|
t_next = self._timesteps[self._step_index + 1]
|
|
|
|
prev_sample = sample_fp32 + (t_next - t) * model_output
|
|
prev_sample = prev_sample.to(model_output.dtype)
|
|
|
|
self._step_index += 1
|
|
|
|
if not return_dict:
|
|
return (prev_sample,)
|
|
return FlowMatchEulerDiscreteSchedulerOutput(prev_sample=prev_sample)
|
|
|
|
def __len__(self) -> int:
|
|
return self.num_train_timesteps
|