From 89fc7686e9959ffc92ff30ce0259551f0e1d4440 Mon Sep 17 00:00:00 2001 From: nraesalmi Date: Wed, 8 Apr 2026 18:24:55 +0300 Subject: [PATCH 1/5] move cleanup logic from ai_summarizer to pipeline; fix memory leak when summary model was not deleted after use; fix encoding error with html --- avise/pipelines/languagemodel/pipeline.py | 41 +++++++++++--------- avise/reportgen/reporters/html_reporter.py | 2 +- avise/reportgen/summarizers/ai_summarizer.py | 13 ------- 3 files changed, 24 insertions(+), 32 deletions(-) diff --git a/avise/pipelines/languagemodel/pipeline.py b/avise/pipelines/languagemodel/pipeline.py index 429650d..a0a78e7 100644 --- a/avise/pipelines/languagemodel/pipeline.py +++ b/avise/pipelines/languagemodel/pipeline.py @@ -161,6 +161,9 @@ def run( connector_config_path: Path to model configuration (for report metadata) generate_ai_summary: Whether to generate AI summary + Returns: + ReportData: The final report with all the SET data + Requirements: Return the final report Calls other class methods with appropriate arguments @@ -170,21 +173,28 @@ def run( self.set_config_path = set_config_path self.target_model_name = connector.model - # Initialize - sets = self.initialize(set_config_path) + try: + # Initialize + sets = self.initialize(set_config_path) - # Execute - execution_data = self.execute(connector, sets) + # Execute + execution_data = self.execute(connector, sets) - # Evaluate - results = self.evaluate(execution_data) + # Evaluate + results = self.evaluate(execution_data) - # Report - report_data = self.report( - results, output_path, report_format, generate_ai_summary - ) + # Report + report_data = self.report( + results, output_path, report_format, generate_ai_summary + ) + + return report_data - return report_data + finally: + if self.evaluation_model: + logger.info("Cleaning up report model after pipeline execution") + self.evaluation_model.del_model() + self.evaluation_model = None def generate_ai_summary( self, @@ -201,9 +211,6 @@ def generate_ai_summary( results: List of EvaluationResult from evaluate() summary_stats: Summary statistics from calculate_passrates() subcategory_runs: Optional dict of subcategory -> number of runs - - Returns: - Dict with ai_summary or None if generation fails """ try: from avise.reportgen.summarizers.ai_summarizer import AISummarizer @@ -213,9 +220,7 @@ def generate_ai_summary( logger.info("Reusing existing evaluation model for AI summary") model_to_use = self.evaluation_model else: - logger.info( - "Creating new model for AI summary (CPU mode due to memory constraints)" - ) + logger.info("Creating new model for AI summary (no existing evaluation model)") model_to_use = None summarizer = AISummarizer(reuse_model=model_to_use) @@ -333,4 +338,4 @@ def _calculate_confidence_interval( lower_bound = max(0, lower_bound) upper_bound = min(1, upper_bound) - return (p, lower_bound, upper_bound) + return (p, lower_bound, upper_bound) \ No newline at end of file diff --git a/avise/reportgen/reporters/html_reporter.py b/avise/reportgen/reporters/html_reporter.py index 7ec4a82..c6fc0c3 100644 --- a/avise/reportgen/reporters/html_reporter.py +++ b/avise/reportgen/reporters/html_reporter.py @@ -26,7 +26,7 @@ def write(self, report_data: ReportData, output_path: Path) -> None: output_path: Path to the output file / directory """ html = self._generate_html(report_data) - with open(output_path, "w") as f: + with open(output_path, "w", encoding='utf-8') as f: f.write(html) def _generate_html(self, report_data: ReportData) -> str: diff --git a/avise/reportgen/summarizers/ai_summarizer.py b/avise/reportgen/summarizers/ai_summarizer.py index 9c0980c..8552ec0 100644 --- a/avise/reportgen/summarizers/ai_summarizer.py +++ b/avise/reportgen/summarizers/ai_summarizer.py @@ -35,16 +35,13 @@ def __init__( from avise.models.evaluation_lm import EvaluationLanguageModel if reuse_model is not None: - logger.info("Reusing existing evaluation model for AI summary") self.model = reuse_model - self._owns_model = False else: logger.info("Loading AI summarizer model...") self.model = EvaluationLanguageModel( model_name=evaluation_model_name, max_new_tokens=max_new_tokens, ) - self._owns_model = True def generate_summary( self, @@ -292,16 +289,6 @@ def _format_results_for_prompt( return "\n".join(lines) - def cleanup(self): - """Clean up the model from memory.""" - if self.model and self._owns_model: - logger.info("Cleaning up AI summarizer model...") - self.model.del_model() - self.model = None - elif self.model: - logger.info("Skipping cleanup - model is shared with evaluation") - - def format_json_ai_summary(ai_summary: AISummary) -> Dict[str, Any]: """Format AI summary for JSON report output. From 35f7e88b453c9b5ae6f83d92ad6dab981ffc800d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 05:34:15 +0000 Subject: [PATCH 2/5] [pre-commit.ci lite] apply automatic fixes --- avise/pipelines/languagemodel/pipeline.py | 6 ++++-- avise/reportgen/reporters/html_reporter.py | 2 +- avise/reportgen/summarizers/ai_summarizer.py | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/avise/pipelines/languagemodel/pipeline.py b/avise/pipelines/languagemodel/pipeline.py index 1992d07..6d26a84 100644 --- a/avise/pipelines/languagemodel/pipeline.py +++ b/avise/pipelines/languagemodel/pipeline.py @@ -229,7 +229,9 @@ def generate_ai_summary( logger.info("Reusing existing evaluation model for AI summary") model_to_use = self.evaluation_model else: - logger.info("Creating new model for AI summary (no existing evaluation model)") + logger.info( + "Creating new model for AI summary (no existing evaluation model)" + ) model_to_use = None summarizer = AISummarizer(reuse_model=model_to_use) @@ -347,4 +349,4 @@ def _calculate_confidence_interval( lower_bound = max(0, lower_bound) upper_bound = min(1, upper_bound) - return (p, lower_bound, upper_bound) \ No newline at end of file + return (p, lower_bound, upper_bound) diff --git a/avise/reportgen/reporters/html_reporter.py b/avise/reportgen/reporters/html_reporter.py index f1f3c5f..6f307f8 100644 --- a/avise/reportgen/reporters/html_reporter.py +++ b/avise/reportgen/reporters/html_reporter.py @@ -26,7 +26,7 @@ def write(self, report_data: ReportData, output_path: Path) -> None: output_path: Path to the output file / directory """ html = self._generate_html(report_data) - with open(output_path, "w", encoding='utf-8') as f: + with open(output_path, "w", encoding="utf-8") as f: f.write(html) def _generate_html(self, report_data: ReportData) -> str: diff --git a/avise/reportgen/summarizers/ai_summarizer.py b/avise/reportgen/summarizers/ai_summarizer.py index 8552ec0..45c326c 100644 --- a/avise/reportgen/summarizers/ai_summarizer.py +++ b/avise/reportgen/summarizers/ai_summarizer.py @@ -289,6 +289,7 @@ def _format_results_for_prompt( return "\n".join(lines) + def format_json_ai_summary(ai_summary: AISummary) -> Dict[str, Any]: """Format AI summary for JSON report output. From 490b490b098fb158685e584ef0196faef5306093 Mon Sep 17 00:00:00 2001 From: Zippo00 Date: Thu, 9 Apr 2026 13:37:46 +0800 Subject: [PATCH 3/5] typo fix --- avise/pipelines/languagemodel/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avise/pipelines/languagemodel/pipeline.py b/avise/pipelines/languagemodel/pipeline.py index 6d26a84..f119973 100644 --- a/avise/pipelines/languagemodel/pipeline.py +++ b/avise/pipelines/languagemodel/pipeline.py @@ -161,7 +161,7 @@ def run( report_format: Desired output format connector_config_path: Path to model configuration (for report metadata) generate_ai_summary: Whether to generate AI summary - runs: How many times to the SET is ran + runs: How many times to run the SET Returns: ReportData: The final report with all the SET data From 114d704b4201a80a07a5d16cc413b82bc42f55e4 Mon Sep 17 00:00:00 2001 From: Zippo00 Date: Thu, 9 Apr 2026 14:03:08 +0800 Subject: [PATCH 4/5] added del_model() method call to BaseSETPipeline.generate_ai_summary() --- avise/models/evaluation_lm.py | 9 +++++---- avise/pipelines/languagemodel/pipeline.py | 9 ++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/avise/models/evaluation_lm.py b/avise/models/evaluation_lm.py index 7524447..53f8458 100644 --- a/avise/models/evaluation_lm.py +++ b/avise/models/evaluation_lm.py @@ -191,10 +191,11 @@ def _mistral_text_generation(self, messages: list) -> str: def del_model(self): """Delete the model from GPU memory.""" - self.model.cpu() - del self.model - del self.tokenizer - torch.cuda.empty_cache() + if self.model: + self.model.cpu() + del self.model + del self.tokenizer + torch.cuda.empty_cache() def _model_download( self, diff --git a/avise/pipelines/languagemodel/pipeline.py b/avise/pipelines/languagemodel/pipeline.py index f119973..7a94f1c 100644 --- a/avise/pipelines/languagemodel/pipeline.py +++ b/avise/pipelines/languagemodel/pipeline.py @@ -201,7 +201,7 @@ def run( finally: if self.evaluation_model: - logger.info("Cleaning up report model after pipeline execution") + # Clean evaluation model from memory self.evaluation_model.del_model() self.evaluation_model = None @@ -224,22 +224,21 @@ def generate_ai_summary( try: from avise.reportgen.summarizers.ai_summarizer import AISummarizer - model_to_use = None + model_to_use = self.evaluation_model if hasattr(self, "evaluation_model") and self.evaluation_model is not None: logger.info("Reusing existing evaluation model for AI summary") - model_to_use = self.evaluation_model else: logger.info( "Creating new model for AI summary (no existing evaluation model)" ) - model_to_use = None summarizer = AISummarizer(reuse_model=model_to_use) results_dict = [r.to_dict() for r in results] ai_summary = summarizer.generate_summary( results_dict, summary_stats, subcategory_runs ) - + # Delete model from memory + summarizer.model.del_model() return { "issue_summary": ai_summary.issue_summary, "recommended_remediations": ai_summary.recommended_remediations, From 2a12032e58f4b60af1bcc40d358a6d90718722f8 Mon Sep 17 00:00:00 2001 From: Zippo00 Date: Thu, 9 Apr 2026 20:24:02 +0800 Subject: [PATCH 5/5] del_model() typo fix --- avise/pipelines/languagemodel/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avise/pipelines/languagemodel/pipeline.py b/avise/pipelines/languagemodel/pipeline.py index 7a94f1c..5ebfba6 100644 --- a/avise/pipelines/languagemodel/pipeline.py +++ b/avise/pipelines/languagemodel/pipeline.py @@ -238,7 +238,7 @@ def generate_ai_summary( results_dict, summary_stats, subcategory_runs ) # Delete model from memory - summarizer.model.del_model() + summarizer.del_model() return { "issue_summary": ai_summary.issue_summary, "recommended_remediations": ai_summary.recommended_remediations,