Skip to content
Merged
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
1 change: 1 addition & 0 deletions application/backend/app/api/routers/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ async def submit_job(
job_id=job_id,
max_calibration_subset_size=job_request.parameters.max_calibration_subset_size,
max_drop=job_request.parameters.max_drop,
max_num_iterations=job_request.parameters.max_num_iterations,
),
)
case JobType.PREPARE_DATASET_FOR_IMPORT:
Expand Down
12 changes: 12 additions & 0 deletions application/backend/app/api/schemas/jobs/quantization.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ class QuantizationRequestParams(BaseModel):
description="Maximum allowed accuracy drop, expressed as a percentage in the range [0-1] "
"where e.g. 0.03 means 3%",
)
max_num_iterations: int | None = Field(
10,
gt=0,
description="Maximum number of iterations for accuracy-aware quantization. "
"A value of null means unlimited. Only used when max_drop is set.",
)

model_config = {
"json_schema_extra": {
"example": {
"model_id": "6b7bb928-5d6f-46ea-8fd2-5ce80dd1e12b",
"max_calibration_subset_size": 100,
"max_drop": 0.01,
"max_num_iterations": 10,
}
}
}
Expand All @@ -53,6 +60,7 @@ class QuantizationRequest(BaseJobRequest):
"model_id": "6b7bb928-5d6f-46ea-8fd2-5ce80dd1e12b",
"max_calibration_subset_size": 100,
"max_drop": 0.01,
"max_num_iterations": 10,
},
}
}
Expand Down Expand Up @@ -85,6 +93,9 @@ class QuantizationMetadata(BaseModel):
model_variant: QuantizationModelVariantMetadata = Field(..., description="Model variant created by quantization")
max_calibration_subset_size: int = Field(..., description="Maximum calibration subset size")
max_drop: float | None = Field(None, description="Maximum allowed accuracy drop")
max_num_iterations: int | None = Field(
None, description="Maximum number of iterations for accuracy-aware quantization (null means unlimited)"
)

@model_validator(mode="before")
@classmethod
Expand All @@ -96,5 +107,6 @@ def populate_metadata(cls, data: object) -> object:
"model_variant": QuantizationModelVariantMetadata(id=data.params.model_variant_id),
"max_calibration_subset_size": data.params.max_calibration_subset_size,
"max_drop": data.params.max_drop,
"max_num_iterations": data.params.max_num_iterations,
}
Comment thread
A-Artemis marked this conversation as resolved.
return data
Original file line number Diff line number Diff line change
Expand Up @@ -238,19 +238,26 @@ def run_quantization(
ov_engine: OVEngine,
subset_size: int,
max_drop: float | None = None,
max_num_iterations: int | None = None,
) -> Path:
"""Execute the quantization process using nncf.quantize() via OVEngine.optimize().

Args:
ov_engine: The OVEngine instance.
subset_size: Maximum calibration subset size.
max_drop: Optional maximum accuracy drop for accuracy-aware quantization.
max_num_iterations: Optional maximum number of iterations for accuracy-aware
quantization. ``None`` means unlimited. Only used when ``max_drop`` is set.

Returns:
Path to the quantized model XML file.
"""
logger.info("Running quantization with max_calibration_subset_size={}", subset_size)
quantized_model_path = ov_engine.optimize(max_data_subset_size=subset_size, max_drop=max_drop)
quantized_model_path = ov_engine.optimize(
max_data_subset_size=subset_size,
max_drop=max_drop,
max_num_iterations=max_num_iterations,
)
logger.info("Quantization completed. Model saved at {}", quantized_model_path)
return quantized_model_path

Expand Down Expand Up @@ -328,16 +335,23 @@ def execute(self, params: QuantizationJobParams) -> None:

datamodule = self.prepare_calibration_dataset(params=params, model=model)
ov_engine = self.initialize_engine(params=params, model=model, datamodule=datamodule)

# max_num_iterations only applies to accuracy-aware quantization (max_drop set).
# For standard PTQ it must always be None regardless of the configured value.
max_num_iterations = params.max_num_iterations if params.max_drop is not None else None

quantized_model_path = self.run_quantization(
ov_engine=ov_engine,
subset_size=params.max_calibration_subset_size,
max_drop=params.max_drop,
max_num_iterations=max_num_iterations,
)

quantization_info = {
"type": "PTQ" if params.max_drop is None else "Accuracy-aware PTQ",
"max_calibration_subset_size": params.max_calibration_subset_size,
"max_drop": params.max_drop,
"max_num_iterations": max_num_iterations,
}

with self._db_session_factory() as db:
Expand Down
4 changes: 4 additions & 0 deletions application/backend/app/models/jobs/quantization_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class QuantizationJobParams(JobParams):
model_variant_id: UUID = Field(default_factory=uuid4)
max_calibration_subset_size: int = Field(default=100, description="Max samples for calibration")
max_drop: float | None = Field(default=None, description="Max accuracy drop for accuracy-aware quantization")
max_num_iterations: int | None = Field(
default=10,
description="Max number of iterations for accuracy-aware quantization (None means unlimited)",
)


class QuantizationJob(ProjectJob[QuantizationJobParams]):
Expand Down
3 changes: 3 additions & 0 deletions application/backend/tests/unit/api/routers/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def test_submit_quantize_job(self, tmp_path, fxt_client, fxt_jobs_queue, fxt_pro
"model_id": model_id,
"max_calibration_subset_size": 200,
"max_drop": 0.01,
"max_num_iterations": 5,
},
}
)
Expand All @@ -145,6 +146,7 @@ def test_submit_quantize_job(self, tmp_path, fxt_client, fxt_jobs_queue, fxt_pro
assert submitted_job.params.model_id == model_id
assert submitted_job.params.max_calibration_subset_size == 200
assert submitted_job.params.max_drop == 0.01
assert submitted_job.params.max_num_iterations == 5
assert submitted_job.project_id == project.id

def test_submit_quantize_job_defaults(self, tmp_path, fxt_client, fxt_jobs_queue, fxt_project_service):
Expand Down Expand Up @@ -177,6 +179,7 @@ def test_submit_quantize_job_defaults(self, tmp_path, fxt_client, fxt_jobs_queue
assert submitted_job.params.model_id == model_id
assert submitted_job.params.max_calibration_subset_size == 100
assert submitted_job.params.max_drop is None
assert submitted_job.params.max_num_iterations == 10

def test_list_jobs(self, fxt_client, fxt_jobs_queue, fxt_job):
fxt_jobs_queue.list_all.return_value = [fxt_job(), fxt_job()]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ def test_run_quantization_standard_ptq(
mock_engine.optimize.assert_called_once_with(
max_data_subset_size=150,
max_drop=None,
max_num_iterations=None,
)

def test_run_quantization_accuracy_aware(
Expand All @@ -359,12 +360,14 @@ def test_run_quantization_accuracy_aware(
ov_engine=mock_engine,
subset_size=200,
max_drop=0.01,
max_num_iterations=5,
)

assert result == expected_path
mock_engine.optimize.assert_called_once_with(
max_data_subset_size=200,
max_drop=0.01,
max_num_iterations=5,
)

def test_run_quantization_propagates_engine_error(
Expand Down Expand Up @@ -869,6 +872,7 @@ def test_execute_standard_ptq(
mock_engine.optimize.assert_called_once_with(
max_data_subset_size=50,
max_drop=None,
max_num_iterations=None,
)
Comment thread
Copilot marked this conversation as resolved.
fxt_model_service.create_variant.assert_called_once_with(
model_revision_id=model_id,
Expand All @@ -878,6 +882,7 @@ def test_execute_standard_ptq(
"type": "PTQ",
"max_calibration_subset_size": 50,
"max_drop": None,
"max_num_iterations": None,
},
model_variant_id=model_variant_id,
)
Expand Down Expand Up @@ -927,6 +932,7 @@ def test_execute_accuracy_aware_ptq(
model_variant_id=model_variant_id,
max_calibration_subset_size=200,
max_drop=0.02,
max_num_iterations=5,
)

model = _make_model_revision(model_id)
Expand Down Expand Up @@ -981,11 +987,13 @@ def test_execute_accuracy_aware_ptq(
mock_engine.optimize.assert_called_once_with(
max_data_subset_size=200,
max_drop=0.02,
max_num_iterations=5,
)
fxt_model_service.create_variant.assert_called_once()
call_kwargs = fxt_model_service.create_variant.call_args.kwargs
assert call_kwargs["quantization_info"]["type"] == "Accuracy-aware PTQ"
assert call_kwargs["quantization_info"]["max_drop"] == 0.02
assert call_kwargs["quantization_info"]["max_num_iterations"] == 5

def test_execute_cleans_workspace_on_failure(
self,
Expand Down
Loading
Loading