Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 0.18.17 (Unreleased)


### Features
* feat: Add task chunking support to Nuke submitter. Frames are now rendered using
the OpenJD TASK_CHUNKING extension with contiguous chunks. Chunk size defaults to 1
(one frame per task, same as before). Increase chunk size to group frames and reduce
per-task overhead. Optional target chunk duration enables dynamic chunk sizing.

### Important
* This version requires a worker agent that supports the TASK_CHUNKING extension.
Service-managed fleets always use a compatible version. If you use customer-managed
fleets, ensure your worker agents are updated before submitting jobs.

## 0.18.16 (2026-01-20)

Added support for training CopyCat nodes. Users are now able to select to submit a CopyCat training job. A CopyCat training job will perform CopyCat training on the render farm, rather than on the local system.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ This library requires:

This package provides a Nuke plugin that creates jobs for AWS Deadline Cloud using the [AWS Deadline Cloud client library][deadline-cloud-client]. Based on the loaded comp it determines the files required, allows the user to specify render options, and builds an [OpenJD template][openjd] that defines the workflow.

The submitter supports [task chunking][task-chunking], which groups multiple frames into contiguous chunks to reduce per-task overhead. When combined with the adaptor's sticky rendering, this provides optimal performance by eliminating both repeated application startup and scene loading time.

[task-chunking]: https://docs.aws.amazon.com/deadline-cloud/latest/developerguide/build-job-bundle-chunking.html

## Adaptor

The Nuke Adaptor implements the [OpenJD][openjd-adaptor-runtime] interface that allows render workloads to launch Nuke and feed it commands. This gives the following benefits:
Expand Down
2 changes: 2 additions & 0 deletions docs/user_guide/using-submitter/render-submissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ The **Job-specific settings** tab has options specific to jobs created in Nuke.
- *Override frame range* - Select this to render a different frame or frame range than is set in Nuke. Frame ranges follow the [Open Job Description](https://github.com/OpenJobDescription/openjd-specifications/wiki/2023-09-Template-Schemas#34111-intrangeexpr) pattern.
- *Use proxy mode* - Manages whether to use [proxy mode](https://learn.foundry.com/nuke/9.0/content/getting_started/managing_scripts/proxy_mode.html) in the submitted job.
- *Continue on error* - If set, try to continue rendering if Nuke encounters an error. If false, in the case of an error the task is failed.
- *Chunk size* - Number of frames to group into each chunk (1-150). Use 1 for one frame per task (default). Higher values group frames into contiguous chunks to reduce per-task overhead. For more information, see [Task chunking for job templates](https://docs.aws.amazon.com/deadline-cloud/latest/developerguide/build-job-bundle-chunking.html).
- *Target chunk duration (seconds)* - When set, the scheduler dynamically adjusts chunk sizes based on observed runtimes of completed chunks, aiming for this duration per chunk. Leave at 0 to use a fixed chunk size for all chunks.
- *Use timeouts* - Whether or not to use user configured timeouts.
- *Render task timeout* - Maximum duration of each action which performs a render. Default is 6 days.
- *Setup timeout* - Maximum duration of each action which sets up the job for rendering, such as scene load. Default is 1 day.
Expand Down
2 changes: 2 additions & 0 deletions src/deadline/nuke_submitter/data_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class RenderSettings:
view_selection: str = field(default="", metadata={"sticky": True})
is_proxy_mode: bool = field(default=False, metadata={"sticky": True})
continue_on_error: bool = field(default=False, metadata={"sticky": True})
chunk_size: int = field(default=1, metadata={"sticky": True})
target_chunk_duration: int = field(default=0, metadata={"sticky": True})


@dataclass
Expand Down
23 changes: 18 additions & 5 deletions src/deadline/nuke_submitter/deadline_submitter_for_nuke.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,10 @@ def _get_job_template(settings: SubmitterUISettings) -> dict[str, Any]:
# Load the default Nuke job template, and then fill in scene-specific
# values it needs.

template_name = (
"default_nuke_job_template.yaml"
if job_type == JobType.RENDER
else "copycat_job_template.yaml"
)
if job_type == JobType.RENDER:
template_name = "default_nuke_job_template.yaml"
else:
template_name = "copycat_job_template.yaml"

with open(Path(__file__).parent / template_name) as f:
job_template = yaml.safe_load(f)
Expand Down Expand Up @@ -364,6 +363,20 @@ def _get_render_parameter_values(
}
)

# Set chunking parameter values
parameter_values.append(
{
"name": "ChunkSize",
"value": settings.jobtype_specific_settings.chunk_size, # type: ignore[union-attr]
}
)
parameter_values.append(
{
"name": "TargetChunkDuration",
"value": settings.jobtype_specific_settings.target_chunk_duration, # type: ignore[union-attr]
}
)

# Set the OCIO config path value
if nuke_ocio.is_OCIO_enabled():
ocio_config_path = nuke_ocio.get_ocio_config_path()
Expand Down
29 changes: 28 additions & 1 deletion src/deadline/nuke_submitter/default_nuke_job_template.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
specificationVersion: jobtemplate-2023-09
extensions:
- TASK_CHUNKING
name: Default Nuke Job Template
parameterDefinitions:
- name: NukeScriptFile
Expand Down Expand Up @@ -34,6 +36,27 @@ parameterDefinitions:
type: STRING
description: The frames to render. E.g. 1-3,8,11-15
minLength: 1
- name: ChunkSize
type: INT
default: 1
minValue: 1
description: >
Number of frames per chunk. Use 1 for one frame per task (default).
Higher values group frames into chunks to reduce per-task overhead.
userInterface:
control: SPIN_BOX
label: Chunk Size
- name: TargetChunkDuration
type: INT
default: 0
minValue: 0
description: >
Optional target duration in seconds per chunk. When set, the scheduler
dynamically adjusts chunk sizes based on observed runtimes of completed
chunks. Set to 0 to use a fixed chunk size for all chunks.
userInterface:
control: SPIN_BOX
label: Target Chunk Duration (Seconds)
- name: WriteNode
type: STRING
userInterface:
Expand Down Expand Up @@ -76,8 +99,12 @@ steps:
parameterSpace:
taskParameterDefinitions:
- name: Frame
type: INT
type: CHUNK[INT]
range: '{{Param.Frames}}'
chunks:
defaultTaskCount: '{{Param.ChunkSize}}'
targetRuntimeSeconds: '{{Param.TargetChunkDuration}}'
rangeConstraint: CONTIGUOUS
stepEnvironments:
- name: Nuke
description: Runs Nuke in the background with a script file loaded.
Expand Down
42 changes: 36 additions & 6 deletions src/deadline/nuke_submitter/ui/components/scene_settings_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
UI widgets for the Scene Settings tab.
"""

import os
import nuke

Expand Down Expand Up @@ -98,6 +99,29 @@ def _build_render_ui_options(self, lyt: QGridLayout):
)
lyt.addWidget(self.continue_on_error_check, 4, 0)

# Chunking controls
lyt.addWidget(QLabel("Chunk size"), 5, 0)
self.chunk_size_spin = QSpinBox(self, minimum=1, maximum=150)
self.chunk_size_spin.setValue(1)
self.chunk_size_spin.setToolTip(
"Number of frames to group into each chunk.\n"
"Use 1 for one frame per task (default).\n"
"Higher values reduce per-task overhead."
)
lyt.addWidget(self.chunk_size_spin, 5, 1, 1, -1)

lyt.addWidget(QLabel("Target chunk duration (seconds)"), 6, 0)
self.target_chunk_duration_spin = QSpinBox(self, minimum=0, maximum=86400)
self.target_chunk_duration_spin.setValue(0)
self.target_chunk_duration_spin.setToolTip(
"When set, the scheduler dynamically adjusts chunk sizes\n"
"based on observed runtimes of completed chunks, aiming\n"
"for this duration per chunk. Leave at 0 to use a fixed\n"
"chunk size for all chunks."
)
self.target_chunk_duration_spin.setSpecialValueText("Disabled")
lyt.addWidget(self.target_chunk_duration_spin, 6, 1, 1, -1)

def _build_copycat_ui_options(self, lyt: QGridLayout):
self.copycat_node_box = QComboBox(self)
self._rebuild_copycat_node_drop_down()
Expand All @@ -118,17 +142,17 @@ def _build_ui(self):
self.timeout_checkbox.setToolTip(
"Set a maximum duration for actions from this job. See AWS Deadline Cloud documentation to learn more"
)
lyt.addWidget(self.timeout_checkbox, 5, 0)
lyt.addWidget(self.timeout_checkbox, 7, 0)
self.timeouts_subtext = QLabel("Set a maximum duration for actions from this job")
self.timeouts_subtext.setStyleSheet("font-style: italic")
lyt.addWidget(self.timeouts_subtext, 5, 1, 1, -1)
lyt.addWidget(self.timeouts_subtext, 7, 1, 1, -1)

self.timeouts_box = QGroupBox()
timeouts_lyt = QGridLayout(self.timeouts_box)
lyt.addWidget(self.timeouts_box, 6, 0, 1, -1)
lyt.addWidget(self.timeouts_box, 8, 0, 1, -1)

self.gizmos_checkbox = QCheckBox("Include gizmos in job bundle", self)
lyt.addWidget(self.gizmos_checkbox, 7, 0)
lyt.addWidget(self.gizmos_checkbox, 9, 0)

def create_timeout_row(label, tooltip, row):
qlabel = QLabel(label)
Expand Down Expand Up @@ -181,9 +205,9 @@ def indicate_is_valid_callback(value: int):
self.include_adaptor_wheels = QCheckBox(
"Developer option: Include adaptor wheels", self
)
lyt.addWidget(self.include_adaptor_wheels, 8, 0, 1, 2)
lyt.addWidget(self.include_adaptor_wheels, 10, 0, 1, 2)

lyt.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 9, 0)
lyt.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 11, 0)

def indicate_if_valid(self, timeout_boxes: tuple[QLabel, QSpinBox, QSpinBox, QSpinBox]):
if (
Expand Down Expand Up @@ -284,6 +308,9 @@ def _refresh_render_ui(self, settings: RenderSettings):
self.proxy_mode_check.setChecked(settings.is_proxy_mode)
self.continue_on_error_check.setChecked(settings.continue_on_error)

self.chunk_size_spin.setValue(settings.chunk_size)
self.target_chunk_duration_spin.setValue(settings.target_chunk_duration)

def _refresh_copycat_ui(self, settings: CopyCatTrainingSettings):
pass

Expand Down Expand Up @@ -324,6 +351,9 @@ def _update_render_settings(self, settings: RenderSettings):
settings.is_proxy_mode = self.proxy_mode_check.isChecked()
settings.continue_on_error = self.continue_on_error_check.isChecked()

settings.chunk_size = self.chunk_size_spin.value()
settings.target_chunk_duration = self.target_chunk_duration_spin.value()

def _update_copycat_training_settings(self, settings: CopyCatTrainingSettings):
settings.copycat_node = self.copycat_node_box.currentData()

Expand Down
Loading
Loading