autoforge 1.9.2__tar.gz → 1.9.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {autoforge-1.9.2 → autoforge-1.9.2.2}/PKG-INFO +1 -1
  2. {autoforge-1.9.2 → autoforge-1.9.2.2}/pyproject.toml +1 -1
  3. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/PKG-INFO +1 -1
  4. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/SOURCES.txt +2 -0
  5. autoforge-1.9.2.2/src/autoforge/Helper/AmpUtils.py +123 -0
  6. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/OptimizerHelper.py +18 -14
  7. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/auto_forge.py +1 -4
  8. autoforge-1.9.2.2/tests/test_amp_autocast_probe.py +52 -0
  9. {autoforge-1.9.2 → autoforge-1.9.2.2}/README.md +0 -0
  10. {autoforge-1.9.2 → autoforge-1.9.2.2}/setup.cfg +0 -0
  11. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/dependency_links.txt +0 -0
  12. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/entry_points.txt +0 -0
  13. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/requires.txt +0 -0
  14. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/AutoForge.egg-info/top_level.txt +0 -0
  15. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/CAdamW.py +0 -0
  16. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/FilamentHelper.py +0 -0
  17. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/Heightmaps/ChristofidesHeightMap.py +0 -0
  18. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/Heightmaps/DepthEstimateHeightMap.py +0 -0
  19. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/Heightmaps/__init__.py +0 -0
  20. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/ImageHelper.py +0 -0
  21. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/OtherHelper.py +0 -0
  22. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/OutputHelper.py +0 -0
  23. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/PruningHelper.py +0 -0
  24. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/__init__.py +0 -0
  25. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Helper/filamentcolors_library.py +0 -0
  26. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Init_comparer/__init__.py +0 -0
  27. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Init_comparer/auto_forge_init_check.py +0 -0
  28. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Init_comparer/plot_results.py +0 -0
  29. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Loss/LossFunctions.py +0 -0
  30. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Loss/PerceptionLoss.py +0 -0
  31. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Loss/__init__.py +0 -0
  32. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Misc/__init__.py +0 -0
  33. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Misc/color_fit_all.py +0 -0
  34. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Modules/Optimizer.py +0 -0
  35. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/Modules/__init__.py +0 -0
  36. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/__init__.py +0 -0
  37. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/__main__.py +0 -0
  38. {autoforge-1.9.2 → autoforge-1.9.2.2}/src/autoforge/priority_mask_editor.py +0 -0
  39. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_cadamw.py +0 -0
  40. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_christofides_height_map.py +0 -0
  41. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_christofides_heightmap.py +0 -0
  42. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_depth_adjusted_height_map.py +0 -0
  43. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_depth_estimate_height_map.py +0 -0
  44. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_end_to_end_optimizer.py +0 -0
  45. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_filament_and_output.py +0 -0
  46. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_filament_helper.py +0 -0
  47. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_flatforge.py +0 -0
  48. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_heightmap_init.py +0 -0
  49. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_image_helper.py +0 -0
  50. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_loss_functions.py +0 -0
  51. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_optimizer_helper.py +0 -0
  52. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_optimizer_module.py +0 -0
  53. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_optimizer_smoke.py +0 -0
  54. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_output_helper.py +0 -0
  55. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_output_helper_extended.py +0 -0
  56. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_perception_and_pruning.py +0 -0
  57. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_property_based.py +0 -0
  58. {autoforge-1.9.2 → autoforge-1.9.2.2}/tests/test_pruning_helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.2
3
+ Version: 1.9.2.2
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "AutoForge"
7
- version="1.9.2"
7
+ version="1.9.2.2"
8
8
  description = "AutoForge is a Python tool for generating 3D printed layered models from an input image."
9
9
  authors = [{ name = "Hendric Voss", email = "hvoss@techfak.uni-bielefeld.de" }]
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AutoForge
3
- Version: 1.9.2
3
+ Version: 1.9.2.2
4
4
  Summary: AutoForge is a Python tool for generating 3D printed layered models from an input image.
5
5
  Author-email: Hendric Voss <hvoss@techfak.uni-bielefeld.de>
6
6
  License: CC BY-NC-SA 4.0
@@ -10,6 +10,7 @@ src/autoforge/__init__.py
10
10
  src/autoforge/__main__.py
11
11
  src/autoforge/auto_forge.py
12
12
  src/autoforge/priority_mask_editor.py
13
+ src/autoforge/Helper/AmpUtils.py
13
14
  src/autoforge/Helper/CAdamW.py
14
15
  src/autoforge/Helper/FilamentHelper.py
15
16
  src/autoforge/Helper/ImageHelper.py
@@ -32,6 +33,7 @@ src/autoforge/Misc/__init__.py
32
33
  src/autoforge/Misc/color_fit_all.py
33
34
  src/autoforge/Modules/Optimizer.py
34
35
  src/autoforge/Modules/__init__.py
36
+ tests/test_amp_autocast_probe.py
35
37
  tests/test_cadamw.py
36
38
  tests/test_christofides_height_map.py
37
39
  tests/test_christofides_heightmap.py
@@ -0,0 +1,123 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import nullcontext, contextmanager
4
+ from typing import Optional, Tuple, Dict
5
+
6
+ import os
7
+ import torch
8
+
9
+ # Cache the selection per device key so CPU/CUDA/MPS don't interfere
10
+ _SELECTED_MAP: Dict[str, Tuple[Optional[torch.dtype], str]] = {}
11
+
12
+
13
+ def _device_key(device: torch.device) -> str:
14
+ if device.type == "cuda":
15
+ idx = device.index if device.index is not None else torch.cuda.current_device()
16
+ return f"cuda:{idx}"
17
+ return device.type
18
+
19
+
20
+ def _probe_backward(device: torch.device, dtype: torch.dtype) -> bool:
21
+ """Try a tiny forward+backward under autocast for given dtype on the device.
22
+
23
+ Returns True if successful, False if a RuntimeError occurs (e.g., oneDNN bf16/f16 backward not supported).
24
+ """
25
+ try:
26
+ if device.type == "mps":
27
+ return False # no stable autocast on MPS yet
28
+ x = torch.randn(8, 8, device=device, dtype=torch.float32, requires_grad=False)
29
+ w = torch.randn(8, 8, device=device, dtype=torch.float32, requires_grad=True)
30
+ target = torch.randn(8, 8, device=device, dtype=torch.float32)
31
+ with torch.autocast(device_type=device.type, dtype=dtype):
32
+ y = x @ w
33
+ loss = torch.nn.functional.mse_loss(y, target)
34
+ loss.backward()
35
+ # Step with a dummy optimizer to ensure gradients are usable
36
+ opt = torch.optim.SGD([w], lr=1e-3)
37
+ opt.step()
38
+ return True
39
+ except Exception:
40
+ return False
41
+
42
+
43
+ def _select_dtype_for_device(device: torch.device) -> Tuple[Optional[torch.dtype], str]:
44
+ """Decide the best autocast dtype for the current device, probing backward support.
45
+
46
+ Returns (dtype or None, reason string).
47
+ None means: do not use autocast; stick to full float32.
48
+ """
49
+ # Allow override via env for debugging
50
+ mode = os.getenv("AUTOFORGE_AMP", default="auto").lower()
51
+
52
+ if mode in {"off", "fp32", "float32"}:
53
+ print("AUTOFORGE_AMP=off: disabling automatic mixed precision")
54
+ return None, "AUTOFORGE_AMP=off"
55
+ if mode in {"bf16", "bfloat16"}:
56
+ # Only honor if probe succeeds
57
+ if _probe_backward(device, torch.bfloat16):
58
+ return torch.bfloat16, "forced bf16"
59
+ print("AUTOFORGE_AMP=bf16: disabling bf16 as probe failed")
60
+ return None, "forced bf16 but probe failed"
61
+ if mode in {"fp16", "float16", "half"}:
62
+ if device.type == "cuda" and _probe_backward(device, torch.float16):
63
+ return torch.float16, "forced f16"
64
+ print("AUTOFORGE_AMP=fp16: disabling fp16 as probe failed or non-CUDA")
65
+ return None, "forced f16 but probe failed or non-CUDA"
66
+
67
+ # Automatic selection
68
+ if device.type == "cuda":
69
+ # Prefer bf16 if it actually works end-to-end
70
+ if torch.cuda.is_bf16_supported() and _probe_backward(device, torch.bfloat16):
71
+ print("Using bf16 autocast on CUDA device")
72
+ return torch.bfloat16, "cuda bf16"
73
+ # Next try fp16 if the GPU supports it
74
+ major, minor = torch.cuda.get_device_capability(device)
75
+ if (major * 10 + minor) >= 53 and _probe_backward(device, torch.float16):
76
+ print("Using fp16 autocast on CUDA device")
77
+ return torch.float16, "cuda f16"
78
+ # Fall back to fp32; enable TF32 to speed up matmuls if available
79
+ torch.backends.cuda.matmul.allow_tf32 = True
80
+ torch.backends.cudnn.allow_tf32 = True
81
+ print("Falling back to fp32 autocast on CUDA device with TF32 enabled")
82
+ return None, "cuda fp32 (tf32 on)"
83
+
84
+ if device.type == "cpu":
85
+ # Some CPUs (AVX512) can do bf16 forward/backward with oneDNN, but many cannot.
86
+ # Probe and use bf16 only if backward succeeds; otherwise stay fp32.
87
+ if _probe_backward(device, torch.bfloat16):
88
+ print("Using bf16 autocast on CPU device")
89
+ return torch.bfloat16, "cpu bf16"
90
+ # No reliable fp16 on CPU for backward; use fp32
91
+ print("Falling back to fp32 autocast on CPU device")
92
+ return None, "cpu fp32"
93
+
94
+ if device.type == "mps":
95
+ print("MPS device detected: autocast not reliably supported, using fp32")
96
+ return None, "mps fp32"
97
+
98
+ # Default: no autocast
99
+ print(f"Unknown device type '{device.type}': disabling autocast, using fp32")
100
+ return None, f"{device.type} fp32"
101
+
102
+
103
+ def get_selected_autocast(device: torch.device) -> Tuple[Optional[torch.dtype], str]:
104
+ key = _device_key(device)
105
+ if key not in _SELECTED_MAP:
106
+ _SELECTED_MAP[key] = _select_dtype_for_device(device)
107
+ return _SELECTED_MAP[key]
108
+
109
+
110
+ @contextmanager
111
+ def safe_autocast(device: torch.device):
112
+ """Context manager that uses autocast only if the runtime probe succeeded.
113
+
114
+ Falls back to a no-op context on unsupported platforms to avoid runtime errors like:
115
+ RuntimeError: DNNL does not support bf16/f16 backward on the platform with avx2_vnni_2.
116
+ """
117
+ dtype, _reason = get_selected_autocast(device)
118
+ if dtype is None:
119
+ with nullcontext():
120
+ yield
121
+ else:
122
+ with torch.autocast(device_type=device.type, dtype=dtype):
123
+ yield
@@ -2,6 +2,7 @@ from contextlib import contextmanager
2
2
 
3
3
  import torch
4
4
  import torch.nn.functional as F
5
+ from autoforge.Helper.AmpUtils import get_selected_autocast
5
6
 
6
7
 
7
8
  @torch.jit.script
@@ -343,22 +344,25 @@ class PrecisionManager:
343
344
  self.autocast_dtype = None
344
345
  self.enabled = False
345
346
 
346
- if device.type == "cuda":
347
- if torch.cuda.is_bf16_supported():
348
- print("Using bfloat16 autocast for CUDA.")
349
- self.autocast_dtype = torch.bfloat16 # fastest if available
350
- self.scaler = torch.cuda.amp.GradScaler()
351
- self.enabled = True
352
- elif _has_fp16(device):
353
- print("Using float16 autocast for CUDA.")
354
- self.autocast_dtype = torch.float16
355
- self.scaler = torch.cuda.amp.GradScaler()
356
- self.enabled = True
357
- else:
358
- print("Using float32 autocast for CUDA.")
359
- # TF32 fallback for very old GPUs
347
+ # Decide dtype once using the shared runtime probe
348
+ dtype, _reason = get_selected_autocast(device)
349
+ self.autocast_dtype = dtype
350
+ # Enable only when a non-None dtype is selected and device supports native CUDA autocast
351
+ self.enabled = dtype is not None and device.type == "cuda"
352
+
353
+ # Use GradScaler only for CUDA float16; bf16 does not need scaling
354
+ if self.enabled and dtype == torch.float16:
355
+ self.scaler = torch.cuda.amp.GradScaler()
356
+ else:
357
+ self.scaler = None
358
+
359
+ # Optional: If CUDA but no AMP selected, allow TF32 for speed on Ampere+
360
+ if device.type == "cuda" and dtype is None:
361
+ try:
360
362
  torch.backends.cuda.matmul.allow_tf32 = True
361
363
  torch.backends.cudnn.allow_tf32 = True
364
+ except Exception:
365
+ pass
362
366
 
363
367
  @contextmanager
364
368
  def autocast(self):
@@ -191,7 +191,6 @@ def parse_args() -> argparse.Namespace:
191
191
 
192
192
  parser.add_argument(
193
193
  "--perform_pruning",
194
- type=bool,
195
194
  default=True,
196
195
  help="Perform pruning after optimization",
197
196
  action=argparse.BooleanOptionalAction,
@@ -199,7 +198,6 @@ def parse_args() -> argparse.Namespace:
199
198
 
200
199
  parser.add_argument(
201
200
  "--fast_pruning",
202
- type=bool,
203
201
  default=True,
204
202
  help="Use fast pruning method",
205
203
  action=argparse.BooleanOptionalAction,
@@ -289,7 +287,6 @@ def parse_args() -> argparse.Namespace:
289
287
 
290
288
  parser.add_argument(
291
289
  "--flatforge",
292
- type=bool,
293
290
  default=False,
294
291
  help="Enable FlatForge mode to generate separate STL files for each color",
295
292
  action=argparse.BooleanOptionalAction,
@@ -461,7 +458,7 @@ def _prepare_background_and_materials(
461
458
  Returns:
462
459
  (bgr_tuple_uint8, background_tensor, material_colors_tensor, material_TDs_tensor)
463
460
  """
464
- bgr_tuple = hex_to_rgb(args.background_color)
461
+ bgr_tuple = tuple(hex_to_rgb(args.background_color))
465
462
  background = torch.tensor(bgr_tuple, dtype=torch.float32, device=device)
466
463
  material_colors = torch.tensor(
467
464
  material_colors_np, dtype=torch.float32, device=device
@@ -0,0 +1,52 @@
1
+ import os
2
+ import torch
3
+ import pytest
4
+
5
+ from autoforge.Helper.AmpUtils import safe_autocast, get_selected_autocast
6
+
7
+
8
+ def _device_from_env():
9
+ if torch.cuda.is_available():
10
+ return torch.device("cuda")
11
+ if torch.backends.mps.is_available():
12
+ return torch.device("mps")
13
+ return torch.device("cpu")
14
+
15
+
16
+ def test_safe_autocast_backward_does_not_crash():
17
+ # Ensure automatic mode
18
+ os.environ.pop("AUTOFORGE_AMP", None)
19
+ device = _device_from_env()
20
+
21
+ x = torch.randn(16, 16, device=device, dtype=torch.float32)
22
+ w = torch.randn(16, 16, device=device, dtype=torch.float32, requires_grad=True)
23
+ target = torch.randn(16, 16, device=device, dtype=torch.float32)
24
+
25
+ dtype, _reason = get_selected_autocast(device)
26
+ # Should not raise regardless of dtype selection
27
+ with safe_autocast(device):
28
+ y = x @ w
29
+ loss = torch.nn.functional.mse_loss(y, target)
30
+ loss.backward()
31
+
32
+ # If CUDA fp16 was selected, ensure grads exist and are finite
33
+ if device.type == "cuda" and dtype == torch.float16:
34
+ assert w.grad is not None
35
+ assert torch.isfinite(w.grad).all()
36
+
37
+
38
+ @pytest.mark.parametrize("force", ["off", "bf16", "fp16"]) # exercise overrides
39
+ def test_env_override_modes(monkeypatch, force):
40
+ monkeypatch.setenv("AUTOFORGE_AMP", force)
41
+ device = _device_from_env()
42
+
43
+ x = torch.randn(8, 8, device=device, dtype=torch.float32)
44
+ w = torch.randn(8, 8, device=device, dtype=torch.float32, requires_grad=True)
45
+ target = torch.randn(8, 8, device=device, dtype=torch.float32)
46
+
47
+ with safe_autocast(device):
48
+ y = x @ w
49
+ loss = torch.nn.functional.mse_loss(y, target)
50
+ loss.backward()
51
+ assert w.grad is not None
52
+
File without changes
File without changes