Fix/oo mfix1 (#8)

* fix: OOM error 1

* fix: OOMfix2

---------

Co-authored-by: James <1561862923@qq.com>
This commit is contained in:
petmycat 2026-08-14 20:57:07 +08:00 committed by GitHub
parent a56b09c262
commit c070f71e16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 90 additions and 15 deletions

View File

@ -119,6 +119,7 @@ class SDTrainer(BaseSDTrainProcess):
self._trigger_binding_initial_parameters = {} self._trigger_binding_initial_parameters = {}
self._trigger_binding_prompt_encoder = None self._trigger_binding_prompt_encoder = None
self._trigger_binding_last_metrics = {} self._trigger_binding_last_metrics = {}
self._trigger_binding_last_metrics_written_step = None
self.dfe: Optional[DiffusionFeatureExtractor] = None self.dfe: Optional[DiffusionFeatureExtractor] = None
self.unconditional_embeds = None self.unconditional_embeds = None
@ -1757,6 +1758,33 @@ class SDTrainer(BaseSDTrainProcess):
setter(previous) setter(previous)
return runtime_context() return runtime_context()
def _write_trigger_binding_metrics(self, loss=None):
if not self.three_phase_enabled or self.runtime_phase not in {'a1', 'a2'}:
return
if self._trigger_binding_last_metrics_written_step == self.step_num:
return
artifact_config = getattr(
self.three_phase_trigger_training.artifacts,
f'phase_{self.runtime_phase}',
)
phase_root = os.path.join(
self.three_phase_trigger_training.run_root or self.save_root,
f'phase_{self.runtime_phase}',
)
metrics_path = os.path.join(phase_root, artifact_config.metrics_file)
os.makedirs(os.path.dirname(metrics_path), exist_ok=True)
record = {
'phase': self.runtime_phase,
'step': self.step_num,
'metrics': self._trigger_binding_last_metrics,
}
if loss is not None:
record['loss'] = float(loss.detach().item() if torch.is_tensor(loss) else loss)
import json
with open(metrics_path, 'a', encoding='utf-8') as handle:
handle.write(json.dumps(record, ensure_ascii=False, sort_keys=True) + '\n')
self._trigger_binding_last_metrics_written_step = self.step_num
def _calculate_trigger_binding_loss( def _calculate_trigger_binding_loss(
self, self,
noisy_latents, noisy_latents,
@ -1875,6 +1903,7 @@ class SDTrainer(BaseSDTrainProcess):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
self.additional_logs[f'phase/{self.runtime_phase}/{key}'] = float(value) self.additional_logs[f'phase/{self.runtime_phase}/{key}'] = float(value)
self.additional_logs[f'phase/{self.runtime_phase}/loss'] = float(loss.detach().item()) self.additional_logs[f'phase/{self.runtime_phase}/loss'] = float(loss.detach().item())
self._write_trigger_binding_metrics(loss)
return loss return loss
def _encode_tst_prompt_variants(self, batch, trigger_prompts, decoy_prompts, dtype): def _encode_tst_prompt_variants(self, batch, trigger_prompts, decoy_prompts, dtype):
@ -3131,17 +3160,6 @@ class SDTrainer(BaseSDTrainProcess):
'parameter_change_proof': proof, 'parameter_change_proof': proof,
}, },
) )
metrics_path = os.path.join(phase_root, artifact_config.metrics_file)
os.makedirs(os.path.dirname(metrics_path), exist_ok=True)
with open(metrics_path, 'a', encoding='utf-8') as handle:
import json
handle.write(json.dumps({
'phase': self.runtime_phase,
'step': self.step_num,
'metrics': self._trigger_binding_last_metrics,
'parameter_change_proof': proof,
}, sort_keys=True) + '\n')
def hook_train_loop(self, batch: Union[DataLoaderBatchDTO, List[DataLoaderBatchDTO]]): def hook_train_loop(self, batch: Union[DataLoaderBatchDTO, List[DataLoaderBatchDTO]]):
if isinstance(batch, list): if isinstance(batch, list):
batch_list = batch batch_list = batch

View File

@ -3,6 +3,7 @@ import contextlib
import importlib import importlib
import inspect import inspect
import os import os
import tempfile
import types import types
import unittest import unittest
from pathlib import Path from pathlib import Path
@ -19,8 +20,8 @@ def _load_runtime_methods():
names = { names = {
'three_phase_enabled', '_load_trigger_binding_modules', '_call_supported', '_first_callable', 'three_phase_enabled', '_load_trigger_binding_modules', '_call_supported', '_first_callable',
'_phase_config', '_activator_component_flags', '_configure_phase_trainability', '_phase_config', '_activator_component_flags', '_configure_phase_trainability',
'hook_add_extra_train_params', '_activator_mode', '_calculate_trigger_binding_loss', 'hook_add_extra_train_params', '_activator_mode', '_write_trigger_binding_metrics',
'_install_trigger_binding_prompt_encoder', 'encode_static_prompt', '_calculate_trigger_binding_loss', '_install_trigger_binding_prompt_encoder', 'encode_static_prompt',
} }
selected = [node for node in class_node.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in names] selected = [node for node in class_node.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in names]
module = ast.Module(body=[ast.ClassDef(name='SDTrainerRuntimeHarness', bases=[], keywords=[], body=selected, decorator_list=[])], type_ignores=[]) module = ast.Module(body=[ast.ClassDef(name='SDTrainerRuntimeHarness', bases=[], keywords=[], body=selected, decorator_list=[])], type_ignores=[])
@ -196,6 +197,26 @@ class ThreePhaseRuntimeTest(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'every training caption must contain'): with self.assertRaisesRegex(ValueError, 'every training caption must contain'):
trainer.sd.get_prompt_embeds(['caption without the required token']) trainer.sd.get_prompt_embeds(['caption without the required token'])
def test_phase_metrics_are_written_independently_and_once_per_step(self):
trainer = self._trainer('a1')
trainer.step_num = 7
trainer._trigger_binding_last_metrics = {'gain': 0.25}
trainer._trigger_binding_last_metrics_written_step = None
with tempfile.TemporaryDirectory() as temp_dir:
trainer.save_root = temp_dir
trainer.three_phase_trigger_training.run_root = temp_dir
trainer.three_phase_trigger_training.artifacts = SimpleNamespace(
phase_a1=SimpleNamespace(metrics_file='metrics.jsonl'),
)
trainer._write_trigger_binding_metrics(torch.tensor(0.5))
trainer._write_trigger_binding_metrics(torch.tensor(0.75))
metrics_path = Path(temp_dir) / 'phase_a1' / 'metrics.jsonl'
records = metrics_path.read_text(encoding='utf-8').splitlines()
self.assertEqual(len(records), 1)
self.assertIn('"step": 7', records[0])
self.assertIn('"loss": 0.5', records[0])
self.assertIn('"gain": 0.25', records[0])
def test_a_phase_loss_receives_shared_latent_noise_timestep_and_target(self): def test_a_phase_loss_receives_shared_latent_noise_timestep_and_target(self):
trainer = self._trainer('a1') trainer = self._trainer('a1')
trainer.device_torch = torch.device('cpu') trainer.device_torch = torch.device('cpu')
@ -223,6 +244,7 @@ class ThreePhaseRuntimeTest(unittest.TestCase):
trainer._trigger_binding_modules = { trainer._trigger_binding_modules = {
'losses': types.SimpleNamespace(calculate_trigger_binding_losses=fake_losses) 'losses': types.SimpleNamespace(calculate_trigger_binding_losses=fake_losses)
} }
trainer._write_trigger_binding_metrics = lambda _loss: None
loss = trainer._calculate_trigger_binding_loss( loss = trainer._calculate_trigger_binding_loss(
noisy, noise, timesteps, batch, {}, 1.0, torch.float32 noisy, noise, timesteps, batch, {}, 1.0, torch.float32
) )

View File

@ -24,6 +24,7 @@ from toolkit.trigger_binding_artifacts import (
save_artifact, save_artifact,
save_checkpoint_manifest, save_checkpoint_manifest,
sha256_bytes, sha256_bytes,
tensor_sha256,
source_fingerprint, source_fingerprint,
) )
@ -73,6 +74,34 @@ class TriggerBindingArtifactsTest(unittest.TestCase):
64, 64,
) )
def test_tensor_hash_supports_bfloat16_scalars_empty_and_noncontiguous_tensors(self):
scalar = torch.tensor(1.25, dtype=torch.bfloat16)
vector = scalar.reshape(1)
self.assertEqual(tensor_sha256(scalar), tensor_sha256(vector))
empty = torch.empty(0, dtype=torch.bfloat16)
self.assertEqual(tensor_sha256(empty), sha256_bytes(b""))
base = torch.arange(12, dtype=torch.float32).reshape(3, 4)
noncontiguous = base.transpose(0, 1)
self.assertFalse(noncontiguous.is_contiguous())
self.assertEqual(
tensor_sha256(noncontiguous),
tensor_sha256(noncontiguous.contiguous()),
)
def test_artifact_round_trip_supports_bfloat16_scalar_tensor(self):
tensors = {
"adapter.scale": torch.tensor(1.0, dtype=torch.bfloat16),
"adapter.weight": torch.ones(2, 2, dtype=torch.bfloat16),
}
with tempfile.TemporaryDirectory() as temp_dir:
path, manifest = self._save(temp_dir, tensors=tensors)
loaded, loaded_manifest = load_artifact(path, expected_type="te_adapter")
self.assertEqual(manifest, loaded_manifest)
self.assertEqual(loaded["adapter.scale"].shape, torch.Size([]))
self.assertTrue(torch.equal(loaded["adapter.scale"], tensors["adapter.scale"]))
def test_fingerprints_are_canonical_and_order_independent(self): def test_fingerprints_are_canonical_and_order_independent(self):
self.assertEqual(config_fingerprint({"a": 1, "b": 2}), config_fingerprint({"b": 2, "a": 1})) self.assertEqual(config_fingerprint({"a": 1, "b": 2}), config_fingerprint({"b": 2, "a": 1}))
self.assertNotEqual(config_fingerprint({"a": 1}), config_fingerprint({"a": 2})) self.assertNotEqual(config_fingerprint({"a": 1}), config_fingerprint({"a": 2}))

View File

@ -129,7 +129,7 @@ def to_json_compatible(value: Any) -> Any:
return {"__type__": "torch.dtype", "value": str(value).removeprefix("torch.")} return {"__type__": "torch.dtype", "value": str(value).removeprefix("torch.")}
if isinstance(value, torch.Tensor): if isinstance(value, torch.Tensor):
tensor = value.detach().cpu().contiguous() tensor = value.detach().cpu().contiguous()
raw = tensor.view(torch.uint8).numpy().tobytes() raw = _tensor_bytes(tensor)
return { return {
"__type__": "torch.Tensor", "__type__": "torch.Tensor",
"dtype": str(tensor.dtype).removeprefix("torch."), "dtype": str(tensor.dtype).removeprefix("torch."),
@ -207,7 +207,13 @@ def decode_rng_state(encoded: Any) -> Any:
def _tensor_bytes(tensor: torch.Tensor) -> bytes: def _tensor_bytes(tensor: torch.Tensor) -> bytes:
return tensor.detach().cpu().contiguous().view(torch.uint8).numpy().tobytes() normalized = tensor.detach().cpu().contiguous()
if normalized.numel() == 0:
return b""
# PyTorch cannot reinterpret a zero-dimensional tensor as a dtype with a
# different element size. Flattening preserves the exact storage bytes and
# also handles BF16, scalar adapter scales, and non-contiguous inputs.
return normalized.reshape(-1).view(torch.uint8).numpy().tobytes()
def tensor_sha256(tensor: torch.Tensor) -> str: def tensor_sha256(tensor: torch.Tensor) -> str: