ACMX 2.136.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
deep_dream_model.cpp
Go to the documentation of this file.
2
4
5#include <torch/cuda.h>
6#include <torch/nn/functional/pooling.h>
7#include <torch/nn/functional/upsampling.h>
8#include <torch/script.h>
9#include <torch/torch.h>
10
11#include <cuda_runtime_api.h>
12#include <opencv2/cudaarithm.hpp>
13#include <opencv2/cudawarping.hpp>
14#include <opencv2/imgproc.hpp>
15#if __has_include(<opencv2/geometry/2d.hpp>)
16#include <opencv2/geometry/2d.hpp>
17#endif
18
19#include <algorithm>
20#include <charconv>
21#include <cmath>
22#include <filesystem>
23#include <limits>
24#include <ostream>
25#include <stdexcept>
26#include <string>
27#include <unordered_set>
28#include <utility>
29
30namespace acmxvk::dream {
31 namespace {
32 constexpr std::uintmax_t MAX_MODEL_BYTES = 2ULL * 1024ULL * 1024ULL * 1024ULL;
33 constexpr std::size_t MAX_LAYERS = 128;
34 constexpr std::int64_t MAX_SOURCE_LAYER_INDEX = 4096;
35 constexpr std::int64_t MAX_TEST_INPUT_SIZE = 256;
36 constexpr int MAX_GRADIENT_ASCENT_ITERATIONS = 100;
37 constexpr float MAX_GRADIENT_ASCENT_STEP = 10.0F;
38 constexpr float GRADIENT_EPSILON = 1.0e-8F;
39 constexpr float MAX_FEEDBACK = 0.99F;
40 constexpr float MIN_ZOOM = 0.9F;
41 constexpr float MAX_ZOOM = 1.1F;
42 constexpr float MAX_ROTATION_DEGREES = 5.0F;
43 constexpr int MAX_DREAM_DIMENSION = 4096;
44 constexpr int MAX_TARGET_CHANNEL = 65535;
45 constexpr int MAX_OCTAVES = 8;
46 constexpr float MIN_OCTAVE_SCALE = 1.1F;
47 constexpr float MAX_OCTAVE_SCALE = 3.0F;
48 constexpr int MAX_JITTER = 64;
49 constexpr int MAX_SMOOTHING = 16;
50
51 [[nodiscard]] c10::IValue require_attribute(
52 const torch::jit::Module &module, const std::string &name) {
53 if (!module.hasattr(name)) {
54 throw std::runtime_error("Deep Dream model is missing metadata attribute '" +
55 name + "'");
56 }
57 return module.attr(name);
58 }
59
60 [[nodiscard]] std::string string_attribute(
61 const torch::jit::Module &module, const std::string &name) {
62 const c10::IValue value = require_attribute(module, name);
63 if (!value.isString()) {
64 throw std::runtime_error("Deep Dream metadata attribute '" + name +
65 "' must be a string");
66 }
67 return value.toStringRef();
68 }
69
70 [[nodiscard]] std::int64_t integer_attribute(
71 const torch::jit::Module &module, const std::string &name) {
72 const c10::IValue value = require_attribute(module, name);
73 if (!value.isInt()) {
74 throw std::runtime_error("Deep Dream metadata attribute '" + name +
75 "' must be an integer");
76 }
77 return value.toInt();
78 }
79
80 [[nodiscard]] std::vector<std::string> string_list_attribute(
81 const torch::jit::Module &module, const std::string &name) {
82 const c10::IValue value = require_attribute(module, name);
83 if (!value.isList()) {
84 throw std::runtime_error("Deep Dream metadata attribute '" + name +
85 "' must be a string list");
86 }
87 std::vector<std::string> result;
88 result.reserve(value.toListRef().size());
89 for (const c10::IValue &entry : value.toListRef()) {
90 if (!entry.isString()) {
91 throw std::runtime_error("Deep Dream metadata attribute '" +
92 name + "' must contain strings");
93 }
94 result.push_back(entry.toStringRef());
95 }
96 return result;
97 }
98
99 [[nodiscard]] std::vector<std::int64_t> integer_list_attribute(
100 const torch::jit::Module &module, const std::string &name) {
101 const c10::IValue value = require_attribute(module, name);
102 if (!value.isIntList()) {
103 throw std::runtime_error("Deep Dream metadata attribute '" + name +
104 "' must be an integer list");
105 }
106 return value.toIntVector();
107 }
108
109 [[nodiscard]] std::vector<double> double_list_attribute(
110 const torch::jit::Module &module, const std::string &name) {
111 const c10::IValue value = require_attribute(module, name);
112 if (!value.isDoubleList()) {
113 throw std::runtime_error("Deep Dream metadata attribute '" + name +
114 "' must be a float list");
115 }
116 return value.toDoubleVector();
117 }
118
119 void validate_normalization(const std::vector<double> &values,
120 bool standard_deviation) {
121 if (values.size() != 3) {
122 throw std::runtime_error(
123 "Deep Dream input normalization must contain three channels");
124 }
125 for (double value : values) {
126 if (!std::isfinite(value) || value < 0.0 || value > 10.0 ||
127 (standard_deviation && value <= 0.0)) {
128 throw std::runtime_error(
129 "Deep Dream input normalization contains an invalid value");
130 }
131 }
132 }
133
135 const torch::jit::Module &module) {
136 if (string_attribute(module, "acmxvk_deep_dream_format") !=
137 "acmxvk-deep-dream") {
138 throw std::runtime_error(
139 "TorchScript file is not an ACMXVK Deep Dream model");
140 }
141 if (integer_attribute(module, "acmxvk_deep_dream_version") != 1) {
142 throw std::runtime_error(
143 "Deep Dream model metadata version is unsupported");
144 }
145
146 ModelMetadata metadata;
147 metadata.architecture = string_attribute(module, "architecture");
148 metadata.default_layer = string_attribute(module, "default_layer");
149 metadata.input_channels =
150 integer_attribute(module, "input_channels");
151 metadata.minimum_input_size =
152 integer_attribute(module, "minimum_input_size");
153 metadata.input_mean = double_list_attribute(module, "input_mean");
154 metadata.input_std = double_list_attribute(module, "input_std");
155 const std::vector<std::string> names =
156 string_list_attribute(module, "layer_names");
157 const std::vector<std::int64_t> source_indices =
158 integer_list_attribute(module, "layer_source_indices");
159
162 "Deep Dream model architecture");
165 "Deep Dream default layer");
166 if (metadata.input_channels != 3 || metadata.minimum_input_size < 32 ||
168 throw std::runtime_error(
169 "Deep Dream model has unsupported input dimensions");
170 }
171 validate_normalization(metadata.input_mean, false);
172 validate_normalization(metadata.input_std, true);
173 if (names.empty() || names.size() > MAX_LAYERS ||
174 names.size() != source_indices.size()) {
175 throw std::runtime_error(
176 "Deep Dream model has an invalid feature-layer table");
177 }
178
179 std::unordered_set<std::string> unique_names;
180 std::int64_t previous_index = -1;
181 for (std::size_t index = 0; index < names.size(); ++index) {
183 "Deep Dream feature-layer name");
184 if (!unique_names.insert(names[index]).second ||
185 source_indices[index] <= previous_index ||
186 source_indices[index] > MAX_SOURCE_LAYER_INDEX) {
187 throw std::runtime_error(
188 "Deep Dream model has an invalid feature-layer table");
189 }
190 metadata.layers.push_back(
191 LayerMetadata{names[index], source_indices[index]});
192 previous_index = source_indices[index];
193 }
194 if (!unique_names.contains(metadata.default_layer)) {
195 throw std::runtime_error(
196 "Deep Dream default layer is absent from the layer table");
197 }
198 return metadata;
199 }
200
201 [[nodiscard]] std::size_t resolve_layer(
202 const ModelMetadata &metadata, std::string_view selector) {
203 if (selector.empty()) {
204 selector = metadata.default_layer;
205 }
206 std::size_t numeric_layer = 0;
207 const char *begin = selector.data();
208 const char *end = begin + selector.size();
209 const auto [position, error] =
210 std::from_chars(begin, end, numeric_layer);
211 if (error == std::errc{} && position == end) {
212 if (numeric_layer >= metadata.layers.size()) {
213 throw std::runtime_error(
214 "Deep Dream layer index is outside the model's range");
215 }
216 return numeric_layer;
217 }
218 const auto match = std::find_if(
219 metadata.layers.begin(), metadata.layers.end(),
220 [selector](const LayerMetadata &layer) {
221 return layer.name == selector;
222 });
223 if (match == metadata.layers.end()) {
224 throw std::runtime_error("Deep Dream layer is not present in model: " +
225 std::string(selector));
226 }
227 return static_cast<std::size_t>(
228 std::distance(metadata.layers.begin(), match));
229 }
230
231 [[nodiscard]] std::vector<torch::Tensor> feature_outputs(
232 const c10::IValue &value) {
233 if (!value.isTensorList()) {
234 throw std::runtime_error(
235 "Deep Dream model forward method must return List[Tensor]");
236 }
237 return value.toTensorVector();
238 }
239
241 if (options.iterations < 1 ||
243 throw std::runtime_error(
244 "Deep Dream iterations must be between 1 and 100");
245 }
246 if (!std::isfinite(options.step_size) || options.step_size <= 0.0F ||
248 throw std::runtime_error(
249 "Deep Dream step size must be greater than 0 and no more than 10");
250 }
251 if (!std::isfinite(options.feedback) || options.feedback < 0.0F ||
252 options.feedback > MAX_FEEDBACK) {
253 throw std::runtime_error(
254 "Deep Dream feedback must be between 0 and 0.99");
255 }
256 if (!std::isfinite(options.zoom) || options.zoom < MIN_ZOOM ||
257 options.zoom > MAX_ZOOM) {
258 throw std::runtime_error(
259 "Deep Dream zoom must be between 0.9 and 1.1");
260 }
261 if (!std::isfinite(options.rotation_degrees) ||
262 std::abs(options.rotation_degrees) > MAX_ROTATION_DEGREES) {
263 throw std::runtime_error(
264 "Deep Dream rotation must be between -5 and 5 degrees");
265 }
266 if (options.max_dimension != 0 &&
267 (options.max_dimension < 64 ||
269 throw std::runtime_error(
270 "Deep Dream working size must be 0 or between 64 and 4096");
271 }
272 if (options.target_channel < -1 ||
274 throw std::runtime_error(
275 "Deep Dream target channel must be -1 or between 0 and 65535");
276 }
277 if (options.octaves < 1 || options.octaves > MAX_OCTAVES) {
278 throw std::runtime_error(
279 "Deep Dream octaves must be between 1 and 8");
280 }
281 if (!std::isfinite(options.octave_scale) ||
282 options.octave_scale < MIN_OCTAVE_SCALE ||
283 options.octave_scale > MAX_OCTAVE_SCALE) {
284 throw std::runtime_error(
285 "Deep Dream octave scale must be between 1.1 and 3.0");
286 }
287 if (options.jitter < 0 || options.jitter > MAX_JITTER) {
288 throw std::runtime_error(
289 "Deep Dream jitter must be between 0 and 64 pixels");
290 }
291 if (options.smoothing < 0 ||
292 options.smoothing > MAX_SMOOTHING) {
293 throw std::runtime_error(
294 "Deep Dream smoothing must be between 0 and 16 pixels");
295 }
296 }
297
298 [[nodiscard]] torch::Tensor normalization_tensor(
299 const std::vector<double> &values, const torch::Device &device,
300 torch::ScalarType scalar_type) {
301 return torch::tensor(
302 values,
303 torch::TensorOptions().dtype(scalar_type).device(device))
304 .view({1, 3, 1, 1});
305 }
306
311
312 void check_cuda(cudaError_t result, std::string_view operation) {
313 if (result != cudaSuccess) {
314 throw std::runtime_error(
315 std::string(operation) + ": " + cudaGetErrorString(result));
316 }
317 }
318 } // namespace
319
320 struct Model::Impl {
321 torch::jit::Module module;
323 std::filesystem::path filename;
324 std::vector<std::vector<std::int64_t>> output_shapes;
326 cv::cuda::GpuMat previous_output_cuda;
327 cv::cuda::GpuMat working_input_cuda;
329 cv::cuda::GpuMat blended_input_cuda;
330 std::size_t selected_layer = 0;
331 int cuda_device = 0;
332 torch::ScalarType scalar_type = torch::kFloat32;
333 std::uint64_t frame_sequence = 0;
334
335 [[nodiscard]] TensorAscentResult optimize(
336 const torch::Tensor &input,
337 const GradientAscentOptions &options);
338 };
339
340 [[nodiscard]] TensorAscentResult Model::Impl::optimize(
341 const torch::Tensor &input,
342 const GradientAscentOptions &options) {
343 const torch::Device device(torch::kCUDA, cuda_device);
344 const torch::Tensor mean =
345 normalization_tensor(metadata.input_mean, device, scalar_type);
346 const torch::Tensor standard_deviation =
347 normalization_tensor(metadata.input_std, device, scalar_type);
348 const torch::Tensor normalized_source =
349 ((input - mean) / standard_deviation).detach();
350 const torch::Tensor minimum = (torch::zeros_like(mean) - mean) /
351 standard_deviation;
352 const torch::Tensor maximum = (torch::ones_like(mean) - mean) /
353 standard_deviation;
354
355 const auto resize_tensor = [](const torch::Tensor &tensor,
356 std::int64_t height,
357 std::int64_t width) {
358 if (tensor.size(2) == height && tensor.size(3) == width) {
359 return tensor;
360 }
361 return torch::nn::functional::interpolate(
362 tensor,
363 torch::nn::functional::InterpolateFuncOptions()
364 .size(std::vector<std::int64_t>{height, width})
365 .mode(torch::kBilinear)
366 .align_corners(false));
367 };
368
369 const std::int64_t input_height = input.size(2);
370 const std::int64_t input_width = input.size(3);
371 std::vector<std::pair<std::int64_t, std::int64_t>> octave_sizes;
372 octave_sizes.reserve(static_cast<std::size_t>(options.octaves));
373 const double minimum_scale = std::min(
374 1.0, std::max(static_cast<double>(metadata.minimum_input_size) /
375 input_height,
376 static_cast<double>(metadata.minimum_input_size) /
377 input_width));
378 for (int octave = options.octaves - 1; octave >= 0; --octave) {
379 const double scale = std::max(
380 minimum_scale,
381 1.0 / std::pow(static_cast<double>(options.octave_scale),
382 octave));
383 const std::int64_t height = std::max<std::int64_t>(
385 static_cast<std::int64_t>(std::lround(input_height * scale)));
386 const std::int64_t width = std::max<std::int64_t>(
388 static_cast<std::int64_t>(std::lround(input_width * scale)));
389 const std::pair<std::int64_t, std::int64_t> size{height, width};
390 if (octave_sizes.empty() || octave_sizes.back() != size) {
391 octave_sizes.push_back(size);
392 }
393 }
394
396 result.processed_width = static_cast<int>(input_width);
397 result.processed_height = static_cast<int>(input_height);
398 result.processed_octaves = static_cast<int>(octave_sizes.size());
399 const std::uint64_t current_frame = frame_sequence++;
400 torch::Tensor dream_input;
401 torch::Tensor previous_source;
402 for (std::size_t octave_index = 0;
403 octave_index < octave_sizes.size(); ++octave_index) {
404 const auto [octave_height, octave_width] =
405 octave_sizes[octave_index];
406 const torch::Tensor octave_source = resize_tensor(
407 normalized_source, octave_height, octave_width);
408 if (!dream_input.defined()) {
409 dream_input = octave_source.detach();
410 } else {
411 const torch::Tensor restored_detail =
412 octave_source - resize_tensor(previous_source,
413 octave_height,
414 octave_width);
415 dream_input =
416 (resize_tensor(dream_input.detach(), octave_height,
417 octave_width) +
418 restored_detail)
419 .clamp(minimum, maximum)
420 .detach();
421 }
422 previous_source = octave_source;
423 dream_input.requires_grad_(true);
424
425 for (int iteration = 0; iteration < options.iterations;
426 ++iteration) {
427 torch::Tensor model_input = dream_input;
428 if (options.jitter > 0) {
429 const std::uint64_t span =
430 static_cast<std::uint64_t>(options.jitter * 2 + 1);
431 const std::uint64_t phase =
432 current_frame * 1315423911ULL +
433 octave_index * 2654435761ULL +
434 static_cast<std::uint64_t>(iteration) * 2246822519ULL;
435 const std::int64_t shift_x =
436 static_cast<std::int64_t>(phase % span) -
437 options.jitter;
438 const std::int64_t shift_y =
439 static_cast<std::int64_t>((phase / span) % span) -
440 options.jitter;
441 model_input = torch::roll(
442 dream_input, {shift_y, shift_x}, {2, 3});
443 }
444 const std::vector<torch::Tensor> outputs =
445 feature_outputs(module.forward({model_input}));
446 if (outputs.size() != metadata.layers.size()) {
447 throw std::runtime_error(
448 "Deep Dream model output count changed during gradient ascent");
449 }
450 const torch::Tensor activation = outputs[selected_layer];
451 torch::Tensor target_activation = activation;
452 if (options.target_channel >= 0) {
453 if (options.target_channel >= activation.size(1)) {
454 throw std::runtime_error(
455 "Deep Dream target channel is outside the selected layer's range");
456 }
457 target_activation =
458 activation.select(1, options.target_channel);
459 }
460 const torch::Tensor loss =
461 target_activation.to(torch::kFloat32).square().mean();
462 if (!torch::isfinite(loss).item<bool>()) {
463 throw std::runtime_error(
464 "Deep Dream activation loss is not finite");
465 }
466 loss.backward();
467
468 torch::Tensor gradient = dream_input.grad();
469 if (!gradient.defined() ||
470 !torch::isfinite(gradient).all().item<bool>()) {
471 throw std::runtime_error(
472 "Deep Dream produced an invalid input gradient");
473 }
474 if (options.smoothing > 0) {
475 const std::int64_t kernel_size =
476 options.smoothing * 2 + 1;
477 gradient = torch::nn::functional::avg_pool2d(
478 gradient,
479 torch::nn::functional::AvgPool2dFuncOptions(
480 {kernel_size, kernel_size})
481 .stride({1, 1})
482 .padding(
483 {options.smoothing, options.smoothing})
484 .count_include_pad(false));
485 }
486 const torch::Tensor mean_gradient =
487 gradient.to(torch::kFloat32).abs().mean();
488 const float gradient_value = mean_gradient.item<float>();
489 if (!std::isfinite(gradient_value)) {
490 throw std::runtime_error(
491 "Deep Dream produced a non-finite input gradient magnitude");
492 }
493 result.activation_loss = loss.item<float>();
494 result.mean_gradient = gradient_value;
495 if (gradient_value <= 0.0F) {
496 dream_input.grad().zero_();
497 continue;
498 }
499
500 {
501 torch::NoGradGuard no_grad;
502 dream_input.add_(
503 gradient *
504 (options.step_size /
505 (mean_gradient + GRADIENT_EPSILON)));
506 dream_input.copy_(torch::maximum(
507 torch::minimum(dream_input, maximum), minimum));
508 }
509 dream_input.grad().zero_();
510 }
511 }
512
513 torch::Tensor output =
514 (dream_input.detach() * standard_deviation + mean)
515 .clamp(0.0F, 1.0F);
516 result.mean_pixel_change =
517 (output.to(torch::kFloat32) - input.to(torch::kFloat32))
518 .abs()
519 .mean()
520 .item<float>();
521 return TensorAscentResult{std::move(output), result};
522 }
523
524 Model::Model(std::unique_ptr<Impl> implementation)
525 : implementation(std::move(implementation)) {}
526
527 Model::Model(Model &&) noexcept = default;
528 Model &Model::operator=(Model &&) noexcept = default;
529 Model::~Model() = default;
530
531 [[nodiscard]] Model Model::load(std::string_view filename, int cuda_device,
532 std::string_view layer, bool use_half) {
534 "Deep Dream model path");
535 const std::filesystem::path model_path =
536 std::filesystem::absolute(filename).lexically_normal();
537 if (!std::filesystem::is_regular_file(model_path)) {
538 throw std::runtime_error("Deep Dream model is not a regular file: " +
539 model_path.string());
540 }
541 input::validate_file_size(model_path, "Deep Dream TorchScript model",
542 MAX_MODEL_BYTES);
543
544 const c10::DeviceIndex device_count = torch::cuda::device_count();
545 if (!torch::cuda::is_available() || device_count == 0) {
546 throw std::runtime_error(
547 "Deep Dream model loading requires an available CUDA device");
548 }
549 if (cuda_device < 0 || cuda_device >= device_count) {
550 throw std::runtime_error("Deep Dream CUDA device index is outside "
551 "the available range");
552 }
553
554 const torch::Device device(torch::kCUDA, cuda_device);
555 torch::jit::Module module = torch::jit::load(model_path.string(), device);
556 const torch::ScalarType scalar_type =
557 use_half ? torch::kFloat16 : torch::kFloat32;
558 module.to(device, scalar_type);
559 module.eval();
560 for (torch::Tensor parameter : module.parameters()) {
561 parameter.set_requires_grad(false);
562 }
563
564 auto implementation = std::make_unique<Impl>();
565 implementation->metadata = read_metadata(module);
566 implementation->selected_layer =
567 resolve_layer(implementation->metadata, layer);
568 implementation->filename = model_path;
569 implementation->cuda_device = cuda_device;
570 implementation->scalar_type = scalar_type;
571 implementation->module = std::move(module);
572
573 const std::int64_t input_size =
574 std::max<std::int64_t>(64, implementation->metadata.minimum_input_size);
575 torch::NoGradGuard no_grad;
576 const torch::Tensor input = torch::zeros(
577 {1, implementation->metadata.input_channels, input_size, input_size},
578 torch::TensorOptions().dtype(scalar_type).device(device));
579 const std::vector<torch::Tensor> outputs = feature_outputs(
580 implementation->module.forward({input}));
581 if (outputs.size() != implementation->metadata.layers.size()) {
582 throw std::runtime_error(
583 "Deep Dream model output count does not match its metadata");
584 }
585 implementation->output_shapes.reserve(outputs.size());
586 for (const torch::Tensor &output : outputs) {
587 if (!output.defined() || output.dim() != 4 || output.size(0) != 1 ||
588 !output.is_floating_point() || !output.device().is_cuda() ||
589 output.get_device() != cuda_device || output.size(2) <= 0 ||
590 output.size(3) <= 0) {
591 throw std::runtime_error(
592 "Deep Dream model returned an invalid feature tensor");
593 }
594 implementation->output_shapes.push_back(output.sizes().vec());
595 }
596 torch::cuda::synchronize(cuda_device);
597 return Model(std::move(implementation));
598 }
599
600 [[nodiscard]] const ModelMetadata &Model::metadata() const {
601 return implementation->metadata;
602 }
603
604 [[nodiscard]] std::size_t Model::selected_layer() const {
605 return implementation->selected_layer;
606 }
607
608 [[nodiscard]] std::size_t Model::selected_channels() const {
609 return static_cast<std::size_t>(
610 implementation->output_shapes[implementation->selected_layer][1]);
611 }
612
614 cv::Mat &rgba, const GradientAscentOptions &options) {
615 validate_gradient_options(options);
616 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols < 1 ||
617 rgba.rows < 1) {
618 throw std::runtime_error(
619 "Deep Dream input must be a non-empty RGBA8 image");
620 }
621
622 cv::Mat working_rgba;
623 const int source_max_dimension = std::max(rgba.cols, rgba.rows);
624 double resize_scale = 1.0;
625 if (options.max_dimension > 0 &&
626 source_max_dimension > options.max_dimension) {
627 resize_scale = static_cast<double>(options.max_dimension) /
628 source_max_dimension;
629 }
630 if (resize_scale < 1.0) {
631 const cv::Size working_size(
632 std::max(1, static_cast<int>(std::lround(rgba.cols * resize_scale))),
633 std::max(1, static_cast<int>(std::lround(rgba.rows * resize_scale))));
634 cv::resize(rgba, working_rgba, working_size, 0.0, 0.0,
635 cv::INTER_AREA);
636 } else {
637 working_rgba = rgba;
638 }
639 if (working_rgba.cols < implementation->metadata.minimum_input_size ||
640 working_rgba.rows < implementation->metadata.minimum_input_size) {
641 throw std::runtime_error(
642 "Deep Dream working image is smaller than the model minimum");
643 }
644
645 cv::Mat dream_source = working_rgba;
646 cv::Mat transformed_feedback;
647 cv::Mat blended_source;
648 if (options.feedback > 0.0F &&
649 !implementation->previous_output.empty() &&
650 implementation->previous_output.type() == working_rgba.type() &&
651 implementation->previous_output.size() == working_rgba.size()) {
652 const cv::Point2f center(
653 static_cast<float>(working_rgba.cols - 1) * 0.5F,
654 static_cast<float>(working_rgba.rows - 1) * 0.5F);
655 const cv::Mat transform = cv::getRotationMatrix2D(
656 center, options.rotation_degrees, options.zoom);
657 cv::warpAffine(implementation->previous_output,
658 transformed_feedback, transform,
659 working_rgba.size(),
660 cv::INTER_LINEAR, cv::BORDER_REFLECT_101);
661 cv::addWeighted(transformed_feedback, options.feedback,
662 working_rgba, 1.0F - options.feedback, 0.0,
663 blended_source);
664 dream_source = blended_source;
665 }
666
667 cv::Mat rgb;
668 cv::cvtColor(dream_source, rgb, cv::COLOR_RGBA2RGB);
669 cv::Mat rgb_float;
670 rgb.convertTo(rgb_float, CV_32FC3, 1.0 / 255.0);
671
672 const torch::Device device(torch::kCUDA,
673 implementation->cuda_device);
674 const torch::Tensor input =
675 torch::from_blob(rgb_float.data, {rgb_float.rows, rgb_float.cols, 3},
676 torch::TensorOptions().dtype(torch::kFloat32))
677 .permute({2, 0, 1})
678 .unsqueeze(0)
679 .to(device, implementation->scalar_type)
680 .contiguous();
681 TensorAscentResult tensor_result =
682 implementation->optimize(input, options);
683 GradientAscentResult result = tensor_result.metrics;
684 torch::Tensor output = std::move(tensor_result.output);
685
686 output = output.squeeze(0)
687 .permute({1, 2, 0})
688 .mul(255.0F)
689 .round()
690 .to(torch::kUInt8)
691 .to(torch::kCPU)
692 .contiguous();
693 torch::cuda::synchronize(implementation->cuda_device);
694
695 cv::Mat dreamed_rgb(rgb.rows, rgb.cols, CV_8UC3,
696 output.data_ptr<std::uint8_t>());
697 cv::Mat dreamed_rgba;
698 cv::cvtColor(dreamed_rgb, dreamed_rgba, cv::COLOR_RGB2RGBA);
699 implementation->previous_output = dreamed_rgba.clone();
700 if (dreamed_rgba.size() != rgba.size()) {
701 cv::Mat restored;
702 cv::resize(dreamed_rgba, restored, rgba.size(), 0.0, 0.0,
703 cv::INTER_LINEAR);
704 dreamed_rgba = restored;
705 }
706 std::vector<cv::Mat> original_channels;
707 std::vector<cv::Mat> dreamed_channels;
708 cv::split(rgba, original_channels);
709 cv::split(dreamed_rgba, dreamed_channels);
710 dreamed_channels[3] = original_channels[3];
711 cv::merge(dreamed_channels, rgba);
712 return result;
713 }
714
716 const cv::cuda::GpuMat &rgba, cv::cuda::GpuMat &output,
717 cv::cuda::Stream &stream, const GradientAscentOptions &options) {
718 validate_gradient_options(options);
719 if (rgba.empty() || rgba.type() != CV_8UC4 || rgba.cols < 1 ||
720 rgba.rows < 1) {
721 throw std::runtime_error(
722 "Deep Dream CUDA input must be a non-empty RGBA8 image");
723 }
724
725 const int source_max_dimension = std::max(rgba.cols, rgba.rows);
726 double resize_scale = 1.0;
727 if (options.max_dimension > 0 &&
728 source_max_dimension > options.max_dimension) {
729 resize_scale = static_cast<double>(options.max_dimension) /
730 source_max_dimension;
731 }
732
733 const cv::cuda::GpuMat *working_rgba = &rgba;
734 if (resize_scale < 1.0) {
735 const cv::Size working_size(
736 std::max(1, static_cast<int>(std::lround(rgba.cols * resize_scale))),
737 std::max(1, static_cast<int>(std::lround(rgba.rows * resize_scale))));
738 cv::cuda::resize(rgba, implementation->working_input_cuda,
739 working_size, 0.0, 0.0, cv::INTER_AREA, stream);
740 working_rgba = &implementation->working_input_cuda;
741 }
742 if (working_rgba->cols <
743 implementation->metadata.minimum_input_size ||
744 working_rgba->rows <
745 implementation->metadata.minimum_input_size) {
746 throw std::runtime_error(
747 "Deep Dream CUDA working image is smaller than the model minimum");
748 }
749
750 const cv::cuda::GpuMat *dream_source = working_rgba;
751 if (options.feedback > 0.0F &&
752 !implementation->previous_output_cuda.empty() &&
753 implementation->previous_output_cuda.type() == CV_8UC4 &&
754 implementation->previous_output_cuda.size() ==
755 working_rgba->size()) {
756 const cv::Point2f center(
757 static_cast<float>(working_rgba->cols - 1) * 0.5F,
758 static_cast<float>(working_rgba->rows - 1) * 0.5F);
759 const cv::Mat transform = cv::getRotationMatrix2D(
760 center, options.rotation_degrees, options.zoom);
761 cv::cuda::warpAffine(
762 implementation->previous_output_cuda,
763 implementation->transformed_feedback_cuda, transform,
764 working_rgba->size(), cv::INTER_LINEAR,
765 cv::BORDER_REFLECT_101, cv::Scalar(), stream);
766 cv::cuda::addWeighted(
767 implementation->transformed_feedback_cuda, options.feedback,
768 *working_rgba, 1.0F - options.feedback, 0.0,
769 implementation->blended_input_cuda, -1, stream);
770 dream_source = &implementation->blended_input_cuda;
771 }
772 stream.waitForCompletion();
773
774 const torch::Device device(torch::kCUDA,
775 implementation->cuda_device);
776 const torch::TensorOptions byte_options =
777 torch::TensorOptions().dtype(torch::kUInt8).device(device);
778 const torch::Tensor source_rgba = torch::from_blob(
779 rgba.data, {rgba.rows, rgba.cols, 4},
780 {static_cast<std::int64_t>(rgba.step), 4, 1}, byte_options);
781 const torch::Tensor working_rgba_tensor = torch::from_blob(
782 dream_source->data,
783 {dream_source->rows, dream_source->cols, 4},
784 {static_cast<std::int64_t>(dream_source->step), 4, 1},
785 byte_options);
786 const torch::Tensor input =
787 working_rgba_tensor.narrow(2, 0, 3)
788 .permute({2, 0, 1})
789 .unsqueeze(0)
790 .to(implementation->scalar_type)
791 .div(255.0F)
792 .contiguous();
793
794 TensorAscentResult tensor_result =
795 implementation->optimize(input, options);
796 const auto rgba_tensor = [](const torch::Tensor &rgb,
797 const torch::Tensor &alpha) {
798 const torch::Tensor rgb_bytes =
799 rgb.squeeze(0)
800 .permute({1, 2, 0})
801 .mul(255.0F)
802 .round()
803 .to(torch::kUInt8);
804 return torch::cat({rgb_bytes, alpha}, 2).contiguous();
805 };
806
807 const torch::Tensor working_output = rgba_tensor(
808 tensor_result.output,
809 working_rgba_tensor.narrow(2, 3, 1));
810 implementation->previous_output_cuda.create(
811 dream_source->rows, dream_source->cols, CV_8UC4);
812 torch::cuda::synchronize(implementation->cuda_device);
813 check_cuda(
814 cudaMemcpy2D(implementation->previous_output_cuda.data,
815 implementation->previous_output_cuda.step,
816 working_output.data_ptr<std::uint8_t>(),
817 static_cast<std::size_t>(dream_source->cols) * 4U,
818 static_cast<std::size_t>(dream_source->cols) * 4U,
819 dream_source->rows, cudaMemcpyDeviceToDevice),
820 "Deep Dream could not preserve its CUDA feedback frame");
821
822 torch::Tensor final_rgb = tensor_result.output;
823 if (dream_source->cols != rgba.cols ||
824 dream_source->rows != rgba.rows) {
825 final_rgb = torch::nn::functional::interpolate(
826 final_rgb,
827 torch::nn::functional::InterpolateFuncOptions()
828 .size(std::vector<std::int64_t>{rgba.rows, rgba.cols})
829 .mode(torch::kBilinear)
830 .align_corners(false));
831 }
832 const torch::Tensor final_output =
833 rgba_tensor(final_rgb, source_rgba.narrow(2, 3, 1));
834 torch::cuda::synchronize(implementation->cuda_device);
835 output.create(rgba.rows, rgba.cols, CV_8UC4);
836 check_cuda(
837 cudaMemcpy2D(output.data, output.step,
838 final_output.data_ptr<std::uint8_t>(),
839 static_cast<std::size_t>(rgba.cols) * 4U,
840 static_cast<std::size_t>(rgba.cols) * 4U, rgba.rows,
841 cudaMemcpyDeviceToDevice),
842 "Deep Dream could not publish its CUDA output frame");
843 return tensor_result.metrics;
844 }
845
846 void Model::print(std::ostream &output) const {
847 output << "Deep Dream model: " << implementation->filename.string()
848 << '\n'
849 << "Deep Dream architecture: "
850 << implementation->metadata.architecture << '\n'
851 << "Deep Dream CUDA device: " << implementation->cuda_device
852 << '\n'
853 << "Deep Dream precision: "
854 << (implementation->scalar_type == torch::kFloat16 ? "FP16"
855 : "FP32")
856 << '\n'
857 << "Deep Dream feature layers: "
858 << implementation->metadata.layers.size() << '\n';
859 for (std::size_t index = 0;
860 index < implementation->metadata.layers.size(); ++index) {
861 const LayerMetadata &layer = implementation->metadata.layers[index];
862 output << " " << index << ": " << layer.name << " (source "
863 << layer.source_index << ')';
864 if (index == implementation->selected_layer) {
865 output << " [selected]";
866 }
867 output << '\n';
868 }
869 const std::vector<std::int64_t> &shape =
870 implementation->output_shapes[implementation->selected_layer];
871 output << "Deep Dream selected activation: ";
872 for (std::size_t index = 0; index < shape.size(); ++index) {
873 if (index != 0) {
874 output << 'x';
875 }
876 output << shape[index];
877 }
878 output << '\n'
879 << "Deep Dream selected channels: " << selected_channels()
880 << '\n';
881 }
882
883} // namespace acmxvk::dream
std::size_t selected_channels() const
GradientAscentResult apply_gradient_ascent(cv::Mat &rgba, const GradientAscentOptions &options={})
static Model load(std::string_view filename, int cuda_device, std::string_view layer={}, bool use_half=false)
const ModelMetadata & metadata() const
std::unique_ptr< Impl > implementation
std::size_t selected_layer() const
void print(std::ostream &output) const
GradientAscentResult apply_gradient_ascent_cuda(const cv::cuda::GpuMat &rgba, cv::cuda::GpuMat &output, cv::cuda::Stream &stream, const GradientAscentOptions &options={})
Model(Model &&) noexcept
std::int64_t integer_attribute(const torch::jit::Module &module, const std::string &name)
std::vector< torch::Tensor > feature_outputs(const c10::IValue &value)
void validate_gradient_options(const GradientAscentOptions &options)
torch::Tensor normalization_tensor(const std::vector< double > &values, const torch::Device &device, torch::ScalarType scalar_type)
void validate_normalization(const std::vector< double > &values, bool standard_deviation)
std::size_t resolve_layer(const ModelMetadata &metadata, std::string_view selector)
ModelMetadata read_metadata(const torch::jit::Module &module)
void check_cuda(cudaError_t result, std::string_view operation)
std::vector< std::int64_t > integer_list_attribute(const torch::jit::Module &module, const std::string &name)
std::vector< double > double_list_attribute(const torch::jit::Module &module, const std::string &name)
c10::IValue require_attribute(const torch::jit::Module &module, const std::string &name)
std::string string_attribute(const torch::jit::Module &module, const std::string &name)
std::vector< std::string > string_list_attribute(const torch::jit::Module &module, const std::string &name)
void validate_file_size(const std::filesystem::path &path, std::string_view context, std::uintmax_t maximum_bytes)
void validate_string(std::string_view value, StringKind kind, std::string_view context, bool allow_empty)
std::vector< double > input_std
std::vector< double > input_mean
std::vector< LayerMetadata > layers
cv::cuda::GpuMat transformed_feedback_cuda
TensorAscentResult optimize(const torch::Tensor &input, const GradientAscentOptions &options)
cv::cuda::GpuMat previous_output_cuda
std::filesystem::path filename
std::vector< std::vector< std::int64_t > > output_shapes