pP4DArgosTranslate.dpr: ``` program pP4DArgosTrans...

Criado em: 19 de fevereiro de 2025

Respondido usando O3 Mini High por Chat01

Pergunta

pP4DArgosTranslate.dpr:

text
program pP4DArgosTranslate; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Types, System.Diagnostics, System.IOUtils, // For TPath and TDirectory Windows, PythonEngine, VarPyth, System.Classes, System.Net.HttpClient, // For downloading System.Net.HttpClientComponent, System.Zip; // For unzipping var PythonEngine: TPythonEngine; PythonModule: TPythonModule; // For structured module management PythonHome: string; PyFuncTranslate: PPyObject; // Global reference to the Python translation function const // --- Embedded Python Initialization Script --- EMBEDDED_PYTHON_SCRIPT_INIT: string = '# initialization.py' + sLineBreak + 'import ctranslate2' + sLineBreak + 'import sentencepiece as spm' + sLineBreak + 'from sacremoses.normalize import MosesPunctNormalizer' + sLineBreak + 'import os' + sLineBreak + 'import logging' + sLineBreak + '' + sLineBreak + 'logging.basicConfig(level=logging.WARNING)' + sLineBreak + 'logger = logging.getLogger(__name__)' + sLineBreak + '' + sLineBreak + '# --- Global Initialization ---' + sLineBreak + 'translators = {}' + sLineBreak + 'normalizers = {}' + sLineBreak + 'sp_processors = {}' + sLineBreak + '' + sLineBreak + 'def init_translator_model(model_name):' + sLineBreak + ' if not model_name.startswith("translate-"):' + sLineBreak + ' raise ValueError("Model name must start with ''translate-''")' + sLineBreak + ' if not model_name.endswith("-1_0_extracted"):' + sLineBreak + ' raise ValueError("Model name must end with ''-1_0_extracted''")' + sLineBreak + ' core = model_name[len("translate-"):-len("-1_0_extracted")]' + sLineBreak + ' parts = core.split("_")' + sLineBreak + ' if len(parts) != 2:' + sLineBreak + ' raise ValueError("Model name does not contain valid source and target languages.")' + sLineBreak + ' source_lang, target_lang = parts[0], parts[1]' + sLineBreak + ' EXTRACT_FOLDER = "argos_models"' + sLineBreak + ' # FIX: Use the model name as provided (it already includes ''_extracted'')' + sLineBreak + ' extracted_folder_path = os.path.join(EXTRACT_FOLDER, model_name)' + sLineBreak + ' subfolders = [f for f in os.listdir(extracted_folder_path) if os.path.isdir(os.path.join(extracted_folder_path, f))]' + sLineBreak + ' if not subfolders:' + sLineBreak + ' raise FileNotFoundError(f"No subfolders found in {extracted_folder_path}.")' + sLineBreak + ' if len(subfolders) > 1:' + sLineBreak + ' logger.warning(f"Multiple subfolders found in {extracted_folder_path}. Using the first one: {subfolders[0]}.")' + sLineBreak + ' model_subfolder_name = subfolders[0]' + sLineBreak + ' MODEL_DIRECTORY = os.path.join(extracted_folder_path, model_subfolder_name, "model")' + sLineBreak + ' SP_MODEL_FILE = os.path.join(extracted_folder_path, model_subfolder_name, "sentencepiece.model")' + sLineBreak + ' normalizer = MosesPunctNormalizer(lang=source_lang)' + sLineBreak + ' sp_processor = spm.SentencePieceProcessor(model_file=SP_MODEL_FILE)' + sLineBreak + ' translator = ctranslate2.Translator(MODEL_DIRECTORY, device="cpu")' + sLineBreak + ' translators[model_name] = translator' + sLineBreak + ' normalizers[model_name] = normalizer' + sLineBreak + ' sp_processors[model_name] = sp_processor' + sLineBreak + ' logger.info(f"Argos Translate components initialized for {source_lang}->{target_lang} as model {model_name}.")' + sLineBreak; // --- Embedded Python Translation Script --- EMBEDDED_PYTHON_SCRIPT_TRANSLATE_OPTIMIZED : string = '# translation_optimized.py' + sLineBreak + 'import logging' + sLineBreak + '' + sLineBreak + 'logger = logging.getLogger(__name__)' + sLineBreak + '' + sLineBreak + 'def perform_translation_optimized(model_name, texts_to_translate):' + sLineBreak + ' if model_name not in translators:' + sLineBreak + ' raise ValueError(f"Model {model_name} has not been initialized.")' + sLineBreak + ' translator = translators[model_name]' + sLineBreak + ' normalizer = normalizers[model_name]' + sLineBreak + ' sp_processor = sp_processors[model_name]' + sLineBreak + ' translated_texts = []' + sLineBreak + ' for text_to_translate in texts_to_translate:' + sLineBreak + ' try:' + sLineBreak + ' normalized_text = normalizer.normalize(text_to_translate)' + sLineBreak + ' tokens = sp_processor.encode(normalized_text, out_type=str)' + sLineBreak + ' translation_result = translator.translate_batch([tokens], beam_size=1)' + sLineBreak + ' translated_tokens = translation_result[0].hypotheses[0]' + sLineBreak + ' translated_text = sp_processor.decode(translated_tokens)' + sLineBreak + ' translated_texts.append(translated_text)' + sLineBreak + ' except Exception as e:' + sLineBreak + ' translated_texts.append(f"Translation Error: {str(e)}")' + sLineBreak + ' return translated_texts' + sLineBreak; procedure SetupCUDAEnvironment; var OldPath: string; begin SetEnvironmentVariable('CUDA_PATH', 'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4'); OldPath := GetEnvironmentVariable('PATH'); SetEnvironmentVariable('PATH', PChar(OldPath + ';C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\bin' + ';C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\libnvvp')); // These loads might not be necessary. LoadLibrary (PChar('C:\Users\user\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\lib\cudnn_graph64_9.dll') ); LoadLibrary (PChar('C:\Users\user\AppData\Local\Programs\Python\Python39\lib\site-packages\ctranslate2\ctranslate2.dll') ); LoadLibrary (PChar('C:\Windows\system32\DriverStore\FileRepository\nvdmui.inf_amd64_fdc98cdf10f69918\nvcuda64.dll') ); end; procedure InitializeCUDAContext; begin with GetPythonEngine do begin ExecString(AnsiString('import torch;' + sLineBreak + 'torch.cuda.init();' + sLineBreak + 'print("CUDA Device:", torch.cuda.get_device_name(0))' + sLineBreak)); CheckError; end; end; // Dummy function example (not actually used) function DoNothing(Self, Args: PPyObject): PPyObject; cdecl; begin Result := GetPythonEngine.ReturnNone; end; procedure InitializePythonEngine; begin PythonEngine := TPythonEngine.Create(nil); PythonEngine.Name := 'PythonEngine'; PythonEngine.DllName := 'C:\Users\user\AppData\Local\Programs\Python\Python39\python39.dll'; // Adjust path PythonHome := 'C:\Users\user\AppData\Local\Programs\Python\Python39'; // Adjust path PythonEngine.SetPythonHome(PWideChar(PythonHome)); PythonEngine.LoadDll; // Create the Python module (good practice) PythonModule := TPythonModule.Create(nil); PythonModule.Engine := PythonEngine; PythonModule.ModuleName := 'delphi_module'; // Add a dummy method for consistency PythonModule.AddMethod('do_nothing', @DoNothing, 'A dummy method.'); end; // --- Function to Check and Create Model Directory --- function CheckAndCreateModelDirectory(const ModelName: string): Boolean; var AppPath: string; ExtractFolder: string; ExpectedModelPath: string; SubDirs: TStringDynArray; begin // 1. Get the application's directory. AppPath := ExtractFilePath(ParamStr(0)); // 2. Check/create the "argos_models" directory. ExtractFolder := TPath.Combine(AppPath, 'argos_models'); if not TDirectory.Exists(ExtractFolder) then begin try TDirectory.CreateDirectory(ExtractFolder); except on E: Exception do begin Writeln('Error creating argos_models directory: ', E.Message); Result := False; // Indicate failure Exit; end; end; end; // 3. Use the provided InstalledModelDir directly to form the expected path ExpectedModelPath := TPath.Combine(ExtractFolder, ModelName + '_extracted'); Writeln('Verificando se existe `' + ExpectedModelPath + '`'); // 4. Check if the expected model directory exists if not TDirectory.Exists(ExpectedModelPath) then begin Writeln('`' + ExpectedModelPath + '` não existe'); Result := False; // Model directory does not exist Exit; end; // 5. Check for subdirectories (there should only be one) SubDirs := TDirectory.GetDirectories(ExpectedModelPath); if Length(SubDirs) <> 1 then begin Result := False; // Incorrect number of subdirectories. Exit; end; // 6. Construct the full path to the model.bin file. ExpectedModelPath := TPath.Combine(SubDirs[0], 'model\model.bin'); // 7. Check if model.bin file exists Result := TFile.Exists(ExpectedModelPath); if not Result then Writeln('Model not found: ', ExpectedModelPath); end; // --- Function to Download a File --- function DownloadFile(const AURL, AFilename: string): Boolean; var HttpClient: TNetHTTPClient; Response: IHTTPResponse; FileStream: TFileStream; begin Result := False; HttpClient := TNetHTTPClient.Create(nil); try // Set the User-Agent header to mimic a browser. THIS IS CRUCIAL. HttpClient.UserAgent := 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'; try Response := HttpClient.Get(AURL); if Assigned(Response) and (Response.StatusCode = 200) then // Check for nil Response begin FileStream := TFileStream.Create(AFilename, fmCreate); // Use TFileStream try FileStream.CopyFrom(Response.ContentStream, 0); // Use CopyFrom Result := True; finally FileStream.Free; end; end else begin if Assigned(Response) then Writeln('HTTP Error: ', Response.StatusCode, ' - ', Response.StatusText) else Writeln('HTTP Error: Could not retrieve response.'); Exit; // Add exit here end; except on E: Exception do begin Writeln('Error downloading file: ', E.Message); Exit; // Add exit here also end; end; finally HttpClient.Free; end; end; // --- Function to Extract a Zip File --- procedure ExtractZipFile(const InputZipFile, OutputExtractPath: string); var ZipFile: TZipFile; begin ZipFile := TZipFile.Create; try ZipFile.Open(InputZipFile, zmRead); try // Extract to the OutputExtractPath folder if not TDirectory.Exists(OutputExtractPath) then TDirectory.CreateDirectory(OutputExtractPath); ZipFile.ExtractAll(OutputExtractPath); finally ZipFile.Close; end; // --- Delete the .argosmodel file --- (Added code) try TFile.Delete(InputZipFile); Writeln('Model file deleted successfully.'); except on E: Exception do Writeln('Warning: Could not delete model file: ', E.Message); end; except on E: Exception do begin Writeln('Error extracting zip file: ', E.Message); // Consider deleting the partially extracted folder here in case of an error end; end; end; // --- Function to find the translation path (direct or pivoted) --- function FindTranslationPath(const SourceLang, TargetLang: string; const ModelList: TArray<TArray<string>>): TStringList; var i, j: Integer; begin Result := TStringList.Create; try // Check for direct translation for i := Low(ModelList) to High(ModelList) do begin if (ModelList[i][2] = SourceLang) and (ModelList[i][3] = TargetLang) then begin Result.Add(Format('translate-%s_%s-1_0', [SourceLang, TargetLang])); Exit; // Direct path found end; end; // Check for English pivot for i := Low(ModelList) to High(ModelList) do begin if (ModelList[i][2] = SourceLang) and (ModelList[i][3] = 'en') then begin for j := Low(ModelList) to High(ModelList) do begin if (Length(ModelList[j]) > 3) and (ModelList[j][2] = 'en') and (ModelList[j][3] = TargetLang) then begin Result.Add(Format('translate-%s_%s-1_0', [SourceLang, 'en'])); Result.Add(Format('translate-%s_%s-1_0', ['en', TargetLang])); Exit; // Indirect path found end; end; end; end; except Result.Free; raise; end; end; // Loads and initializes the specified translation models. procedure InitializeArgosTranslate(const ModelNames: TArray<string>); var pyArgs, pyInitFunc, pyMainModule: PPyObject; i: Integer; excType, excValue, excTraceback: PPyObject; pyModelName: PPyObject; AppPath: string; // Application path ModelName, ModelUrl, DownloadedFilePath, ExtractPath: String; begin with GetPythonEngine do begin // 1. Execute the Python initialization script ExecString(AnsiString(EMBEDDED_PYTHON_SCRIPT_INIT)); if PyErr_Occurred <> nil then begin PyErr_Fetch(excType, excValue, excTraceback); try if Assigned(excType) then raise Exception.Create (Format('Error running EmbeddedPythonScriptInit: %s - %s - Traceback: %s', [UTF8ToString(PyObjectAsString(excType)), UTF8ToString(PyObjectAsString(excValue)), UTF8ToString(PyObjectAsString(excTraceback))])) finally PyErr_Clear; Py_XDECREF(excType); Py_XDECREF(excValue); Py_XDECREF(excTraceback); end; end; // Get the __main__ module pyMainModule := GetMainModule; if not Assigned(pyMainModule) then raise Exception.Create('Could not get __main__ module.'); CheckError; // 2. Get the init_translator_model function from __main__ pyInitFunc := PyObject_GetAttrString(pyMainModule, 'init_translator_model'); Py_XDECREF(pyMainModule); // No longer need __main__ if not Assigned(pyInitFunc) then raise Exception.Create('init_translator_model function not found'); CheckError; AppPath := ExtractFilePath(ParamStr(0)); // Get application path // 3. Loop over each model name and initialize it for i := Low(ModelNames) to High(ModelNames) do begin ModelName := ModelNames[i]; // --- Check if Model Exists (and Download if Necessary) --- if not CheckAndCreateModelDirectory(ModelName) then // Check using the correct directory name begin Writeln(Format('Model directory for %s does not exist. Downloading...', [ModelName + '_extracted'])); // 1. Construct download URL ModelUrl := Format('https://argos-net.com/v1/%s.argosmodel', [ModelName] ); // Corrected URL // 2. Define download path DownloadedFilePath := TPath.Combine(AppPath, TPath.Combine('argos_models', ModelName + '.argosmodel')); // Correct file extension // 3. Download if not DownloadFile(ModelUrl, DownloadedFilePath) then begin Writeln(Format('Failed to download model from %s', [ModelUrl])); Continue; // Skip to the next model if download fails end; Writeln(Format('Model downloaded to %s. Extracting...', [DownloadedFilePath])); // 4. Extract to a consistent location ExtractPath := TPath.Combine(AppPath, 'argos_models\' + ModelName + '_extracted'); // Extract *into* argos_models try ExtractZipFile(DownloadedFilePath, ExtractPath); // Unzip directly Writeln(Format('Model extracted to %s.', [ExtractPath])); except on E: Exception do begin Writeln(Format('Model extraction failed: %s', [E.Message])); Continue; // Skip to next model if extraction fails end; end; // 5. Re-check if model exists after download/extraction. VERY IMPORTANT. if not CheckAndCreateModelDirectory(ModelName) then begin Writeln(Format ('Model %s still not found after download and extraction.', [ModelName + '_extracted'])); Continue; // Skip to the next model if it's STILL missing. end; end else begin Writeln(Format('Model directory for %s exists.', [ModelName + '_extracted'])); end; // --- End Check/Download --- // Correctly create a Python string for the model name pyModelName := PyUnicode_FromString (PAnsiChar(UTF8String(ModelNames[i] + '_extracted'))); if not Assigned(pyModelName) then raise Exception.Create ('Could not create Python string for model name.'); pyArgs := MakePyTuple([pyModelName]); // Use MakePyTuple Py_XDECREF(pyModelName); // Decrement reference, as MakePyTuple *increments*. try PyObject_CallObject(pyInitFunc, pyArgs); // Call the init function if PyErr_Occurred <> nil then begin PyErr_Fetch(excType, excValue, excTraceback); try if Assigned(excType) then raise Exception.Create (Format('Error calling init_translator_model(%s): %s - %s - Traceback: %s', [ModelNames[i], UTF8ToString(PyObjectAsString(excType)), UTF8ToString(PyObjectAsString(excValue)), UTF8ToString(PyObjectAsString(excTraceback))])) else raise Exception.Create (Format('Error calling init_translator_model(%s): Unknown Python Error', [ModelNames[i]])); finally PyErr_Clear; Py_XDECREF(excType); Py_XDECREF(excValue); Py_XDECREF(excTraceback); end; end; finally Py_XDECREF(pyArgs); // Clean up arguments end; end; Py_XDECREF(pyInitFunc); // Clean up function reference. // --- 4. Load the translation script --- ExecString(AnsiString(EMBEDDED_PYTHON_SCRIPT_TRANSLATE_OPTIMIZED)); if PyErr_Occurred <> nil then begin PyErr_Fetch(excType, excValue, excTraceback); try if Assigned(excType) then raise Exception.Create (Format('Error running EmbeddedPythonScriptTranslateOptimized: %s - %s - Traceback: %s', // ADDED TRACEBACK HERE [string(PyObjectAsString(excType)), string(PyObjectAsString(excValue)), string(PyObjectAsString(excTraceback))])) else raise Exception.Create ('Error running EmbeddedPythonScriptTranslateOptimized: Unknown Python Error'); finally PyErr_Clear; Py_XDECREF(excType); Py_XDECREF(excValue); Py_XDECREF(excTraceback); end; end; // 5. Get a reference to perform_translation_optimized PyFuncTranslate := PyObject_GetAttrString(GetMainModule, 'perform_translation_optimized'); // from __main__ if not Assigned(PyFuncTranslate) then raise Exception.Create ('Could not find perform_translation_optimized function in __main__.'); CheckError; end; end; // Calls the optimized translation function, expecting a model name and a list of texts. function CallOptimizedTranslate(const ModelName, TextToTranslate : string): string; var pyArgsTuple: PPyObject; pyTextList: PPyObject; pyResultList: PPyObject; pySingleTranslation: PPyObject; excType, excValue, excTraceback: PPyObject; pyModelName: PPyObject; begin with GetPythonEngine do begin if not Assigned(PyFuncTranslate) then begin Result := 'Error: Translation function not initialized.'; Exit; end; // 1. Create a Python list with the text to translate pyTextList := PyList_New(1); if not Assigned(pyTextList) then raise Exception.Create('Failed to create Python list for text.'); PyList_SetItem(pyTextList, 0, PyUnicode_FromString(PAnsiChar(UTF8String(TextToTranslate)))); // No need to check for errors immediately after PyList_SetItem, it *steals* // Convert ModelName to a Python String pyModelName := PyUnicode_FromString (PAnsiChar(UTF8String(ModelName + '_extracted'))); if not Assigned(pyModelName) then begin // If creating the model name string fails, release the text list before raising exception. Py_DECREF(pyTextList); // PyList_SetItem *stole* the reference, so we MUST DECREF here. raise Exception.Create('Failed to create Python string for model name'); end; // 2. Build argument tuple: (model_name, [text]) pyArgsTuple := MakePyTuple([pyModelName, pyTextList]); // Create a tuple. Py_DECREF(pyModelName); // Decref after use. try // we created both objects, so it's safe // 3. Call the Python function pyResultList := PyObject_CallObject(PyFuncTranslate, pyArgsTuple); if PyErr_Occurred <> nil then begin PyErr_Fetch(excType, excValue, excTraceback); try if Assigned(excType) then Result := Format ('Python error during translation: %s - %s - Traceback: %s', [UTF8ToString(PyObjectAsString(excType)), UTF8ToString(PyObjectAsString(excValue)), UTF8ToString(PyObjectAsString(excTraceback))]) else Result := 'Python error during translation: Unknown error'; finally PyErr_Clear; // VERY IMPORTANT! Py_XDECREF(excType); Py_XDECREF(excValue); Py_XDECREF(excTraceback); end; Exit; end; finally Py_XDECREF(pyArgsTuple); // Clean up the argument tuple. // DO NOT DECREF PyTextList! It was *stolen* by PyTuple_SetItem. end; if not Assigned(pyResultList) then begin Result := 'Error: No result returned from Python.'; Exit; end; try // Add try...finally for PyResultList // 4. Expect a Python LIST back. if not PyList_Check(pyResultList) then begin Result := 'Error: Unexpected result from Python - not a list.'; Exit; end; if PyList_Size(pyResultList) = 0 then begin Result := 'Error: Empty result list from Python.'; Exit; end; // 5. Extract the translated string. pySingleTranslation := PyList_GetItem(pyResultList, 0); // Borrowed reference, don't DECREF if not Assigned(pySingleTranslation) then begin Result := 'Error: could not extract translated item.'; Exit; end; Result := UTF8ToString(PyUnicode_AsUTF8(pySingleTranslation)); // Extract string. CheckError; finally Py_XDECREF(pyResultList); // Clean up the result list. end; end; end; procedure DestroyEngine; begin if Assigned(PyFuncTranslate) then PythonEngine.Py_XDECREF(PyFuncTranslate); // Clean up the function reference. ESSENTIAL. if Assigned(PythonModule) then PythonModule.Free; // Free the module *before* the engine if Assigned(PythonEngine) then PythonEngine.Free; end; const ModelList: TArray<TArray<string>> = [ // Block 1 (rows 0 to 11) ['Albanian', 'English', 'sq', 'en', '1.9'], ['Arabic', 'English', 'ar', 'en', '1.0'], ['Azerbaijani', 'English', 'az', 'en', '1.5'], ['Basque', 'English', 'eu', 'en', '1.9'], ['Bengali', 'English', 'bn', 'en', '1.9'], ['Bulgarian', 'English', 'bg', 'en', '1.9'], ['Catalan', 'English', 'ca', 'en', '1.7'], ['Chinese (traditional)', 'English', 'zt', 'en', '1.9'], ['Chinese', 'English', 'zh', 'en', '1.9'], ['Czech', 'English', 'cs', 'en', '1.9.6'], ['Danish', 'English', 'da', 'en', '1.9'], ['Dutch', 'English', 'nl', 'en', '1.8'], // Block 2 (rows 12 to 56): translations where English is the source language ['English', 'Albanian', 'en', 'sq', '1.9'], ['English', 'Arabic', 'en', 'ar', '1.0'], ['English', 'Azerbaijani', 'en', 'az', '1.5'], ['English', 'Basque', 'en', 'eu', '1.9'], ['English', 'Bengali', 'en', 'bn', '1.9'], ['English', 'Bulgarian', 'en', 'bg', '1.9'], ['English', 'Catalan', 'en', 'ca', '1.9'], ['English', 'Chinese', 'en', 'zh', '1.9'], ['English', 'Chinese (traditional)', 'en', 'zt', '1.9'], ['English', 'Czech', 'en', 'cs', '1.9.6'], ['English', 'Danish', 'en', 'da', '1.9'], ['English', 'Dutch', 'en', 'nl', '1.8'], ['English', 'Esperanto', 'en', 'eo', '1.5'], ['English', 'Estonian', 'en', 'et', '1.9'], ['English', 'Finnish', 'en', 'fi', '1.9'], ['English', 'French', 'en', 'fr', '1.9'], ['English', 'Galician', 'en', 'gl', '1.9'], ['English', 'German', 'en', 'de', '1.0'], ['English', 'Greek', 'en', 'el', '1.9'], ['English', 'Hebrew', 'en', 'he', '1.5'], ['English', 'Hindi', 'en', 'hi', '1.1'], ['English', 'Hungarian', 'en', 'hu', '1.9'], ['English', 'Indonesian', 'en', 'id', '1.9'], ['English', 'Irish', 'en', 'ga', '1.1'], ['English', 'Italian', 'en', 'it', '1.0'], ['English', 'Japanese', 'en', 'ja', '1.1'], ['English', 'Korean', 'en', 'ko', '1.1'], ['English', 'Latvian', 'en', 'lv', '1.9'], ['English', 'Lithuanian', 'en', 'lt', '1.9'], ['English', 'Malay', 'en', 'ms', '1.9'], ['English', 'Norwegian', 'en', 'nb', '1.9'], ['English', 'Persian', 'en', 'fa', '1.5'], ['English', 'Polish', 'en', 'pl', '1.9'], ['English', 'Portuguese', 'en', 'pt', '1.0'], ['English', 'Romanian', 'en', 'ro', '1.9'], ['English', 'Russian', 'en', 'ru', '1.9'], ['English', 'Slovak', 'en', 'sk', '1.9'], ['English', 'Slovenian', 'en', 'sl', '1.9'], ['English', 'Spanish', 'en', 'es', '1.0'], ['English', 'Swedish', 'en', 'sv', '1.5'], ['English', 'Tagalog', 'en', 'tl', '1.9'], ['English', 'Thai', 'en', 'th', '1.9'], ['English', 'Turkish', 'en', 'tr', '1.5'], ['English', 'Ukranian', 'en', 'uk', '1.4'], ['English', 'Urdu', 'en', 'ur', '1.9'], // Block 3 (rows 57 to 91): translations from other languages to English ['Esperanto', 'English', 'eo', 'en', '1.5'], ['Estonian', 'English', 'et', 'en', '1.9'], ['Finnish', 'English', 'fi', 'en', '1.9'], ['French', 'English', 'fr', 'en', '1.9'], ['Galician', 'English', 'gl', 'en', '1.9'], ['German', 'English', 'de', 'en', '1.0'], ['Greek', 'English', 'el', 'en', '1.9'], ['Hebrew', 'English', 'he', 'en', '1.5'], ['Hindi', 'English', 'hi', 'en', '1.1'], ['Hungarian', 'English', 'hu', 'en', '1.9'], ['Indonesian', 'English', 'id', 'en', '1.9'], ['Irish', 'English', 'ga', 'en', '1.1'], ['Italian', 'English', 'it', 'en', '1.0'], ['Japanese', 'English', 'ja', 'en', '1.1'], ['Korean', 'English', 'ko', 'en', '1.1'], ['Latvian', 'English', 'lv', 'en', '1.9'], ['Lithuanian', 'English', 'lt', 'en', '1.9'], ['Malay', 'English', 'ms', 'en', '1.9'], ['Norwegian', 'English', 'nb', 'en', '1.9'], ['Persian', 'English', 'fa', 'en', '1.5'], ['Polish', 'English', 'pl', 'en', '1.9'], ['Portuguese', 'English', 'pt', 'en', '1.0'], ['Portuguese', 'Spanish', 'pt', 'es', '1.0'], ['Romanian', 'English', 'ro', 'en', '1.9'], ['Russian', 'English', 'ru', 'en', '1.9'], ['Slovak', 'English', 'sk', 'en', '1.9'], ['Slovenian', 'English', 'sl', 'en', '1.9'], ['Spanish', 'English', 'es', 'en', '1.9'], ['Spanish', 'Portuguese', 'es', 'pt', '1.0'], ['Swedish', 'English', 'sv', 'en', '1.5'], ['Tagalog', 'English', 'tl', 'en', '1.9'], ['Thai', 'English', 'th', 'en', '1.9'], ['Turkish', 'English', 'tr', 'en', '1.5'], ['Ukrainian', 'English', 'uk', 'en', '1.9'], ['Urdu', 'English', 'ur', 'en', '1.9'] ]; var TotalStopwatch: TStopwatch; CreateEngineTime, TranslationTime, DestroyEngineTime: Int64; ModelName1, ModelName2: string; Models: TArray<string>; UserInputText: string; SelectedModel: string; excType, excValue, excTraceback: PPyObject; ModelChoice: Integer; SourceLang, TargetLang: string; ModelName: string; begin try MaskFPUExceptions(True); // Do this very early! TotalStopwatch := TStopwatch.StartNew; // Setup CUDA environment once SetupCUDAEnvironment; CreateEngineTime := 0; var EngineTimer := TStopwatch.StartNew; Writeln('=== Interactive Translation ==='); Writeln('Enter source language 1 code (e.g., ''en'', ''pt'', ''es''):'); Readln(SourceLang); Writeln('Enter target language 1 code (e.g., ''en'', ''pt'', ''es''):'); Readln(TargetLang); var ChosenModels: TStringList := TStringList.Create; ChosenModels := FindTranslationPath(SourceLang, TargetLang, ModelList); try InitializePythonEngine; // Moved before model loading EngineTimer.Stop; CreateEngineTime := EngineTimer.ElapsedMilliseconds; case ChosenModels.Count of 0: // No models returned begin raise Exception.Create('No translation path found for ' + SourceLang + ' -> ' + TargetLang); end; 1: // Direct translation begin // Convert ChosenModels (TStringList) to ChosenModelsArray (TArray<string>) var ChosenModelsArray: TArray<string>; SetLength(ChosenModelsArray, ChosenModels.Count); for var i := 0 to ChosenModels.Count - 1 do ChosenModelsArray[i] := ChosenModels[i]; InitializeArgosTranslate(ChosenModelsArray); // Now takes array of model names EngineTimer.Stop; CreateEngineTime := EngineTimer.ElapsedMilliseconds; InitializeCUDAContext; // Construct the model ModelName := ChosenModels[0]; Writeln(Format('Please enter text to translate using model %s:', [ModelName])); Readln(UserInputText); var TranslationTimer: TStopwatch := TStopwatch.StartNew; var TranslatedText := CallOptimizedTranslate(ModelName, UserInputText); // Pass the selected model TranslationTimer.Stop; TranslationTime := TranslationTimer.ElapsedMilliseconds; Writeln('=== Translation Result (Optimized) ==='); Writeln('Model: ', ModelName); // Display the selected model. Writeln('Original: ', UserInputText); Writeln('Translated: ', TranslatedText); Writeln(''); Writeln('=== Performance Metrics (ms) ==='); Writeln('Create Python Engine (including init): ', CreateEngineTime); Writeln('Translation Time (Delphi Call): ', TranslationTime); end; 2: begin // Convert ChosenModels (TStringList) to ChosenModelsArray (TArray<string>) var ChosenModelsArray: TArray<string>; SetLength(ChosenModelsArray, ChosenModels.Count); for var i := 0 to ChosenModels.Count - 1 do ChosenModelsArray[i] := ChosenModels[i]; InitializeArgosTranslate(ChosenModelsArray); // Now takes array of model names InitializeCUDAContext; {First Translation} // Construct the model ModelName := ChosenModels[0]; Writeln(Format('Please enter text to translate using model %s:', [ModelName])); Readln(UserInputText); var TranslationTimer: TStopwatch := TStopwatch.StartNew; var TranslatedText := CallOptimizedTranslate(ModelName, UserInputText); // Pass the selected model TranslationTimer.Stop; TranslationTime := TranslationTimer.ElapsedMilliseconds; Writeln('=== Translation Result (Optimized) ==='); Writeln('Model: ', ModelName); // Display the selected model. Writeln('Original: ', UserInputText); Writeln('Translated: ', TranslatedText); Writeln(''); Writeln('=== Performance Metrics (ms) ==='); Writeln('Create Python Engine (including init): ', CreateEngineTime); Writeln('Translation Time (Delphi Call): ', TranslationTime); {Second Translation} // Construct the model ModelName := ChosenModels[1]; UserInputText := TranslatedText; TranslationTimer := TStopwatch.StartNew; TranslatedText := CallOptimizedTranslate(ModelName, UserInputText); // Pass the selected model TranslationTimer.Stop; TranslationTime := TranslationTimer.ElapsedMilliseconds; Writeln('=== Translation Result (Optimized) ==='); Writeln('Model: ', ModelName); // Display the selected model. Writeln('Original: ', UserInputText); Writeln('Translated: ', TranslatedText); Writeln(''); Writeln('=== Performance Metrics (ms) ==='); Writeln('Create Python Engine (including init): ', CreateEngineTime); Writeln('Translation Time (Delphi Call): ', TranslationTime); end; end; finally // Moved DestroyEngine here, so it's called *after* timing DestroyEngineTime := 0; var DestroyEngineTimer := TStopwatch.StartNew; DestroyEngine; // GOOD: Clean up in a finally block DestroyEngineTimer.Stop; DestroyEngineTime := DestroyEngineTimer.ElapsedMilliseconds; end; except on E: Exception do begin Writeln(E.ClassName, ': ', E.Message); // Delphi exception // Get and display the Python traceback (if available) GetPythonEngine.PyErr_Fetch(excType, excValue, excTraceback); try if Assigned(excTraceback) then begin Writeln('Python Traceback (most recent call last):'); Writeln(UTF8ToString(GetPythonEngine.PyObjectAsString(excTraceback))); // Print Python traceback end; finally GetPythonEngine.PyErr_Clear; GetPythonEngine.Py_XDECREF(excType); GetPythonEngine.Py_XDECREF(excValue); GetPythonEngine.Py_XDECREF(excTraceback); end; end; end; Writeln('Press Enter to exit...'); Readln; end. initialization // Put engine creation/destruction here to avoid multiple creations InitializePythonEngine; finalization DestroyEngine; // GOOD. Do cleanup. end.

You are an expert Delphi and Python developer, specializing in creating robust and efficient applications that leverage the strengths of both languages. You are highly skilled in using the Python4Delphi (P4D) library to integrate Python code and libraries into Delphi applications. You are also familiar with the Argos Translate library. You prioritize code correctness, memory safety, clear error handling, and adherence to best practices for both Delphi and Python. You are assisting in the development of a Delphi console application.

Project: Delphi Console Application for Offline Text Translation with Argos Translate and P4D

Goal:

The primary goal is to create a Delphi console application that allows users to perform offline text translation using the Argos Translate library. The application should:

  1. Allow the user to specify the source and target languages.
  2. Automatically construct the required Argos Translate model name based on the user's language choices (e.g., "en" and "es" become "translate-en_es-1_0_extracted").
  3. Check if the required translation model(s) are already installed locally.
  4. If a required model is not installed:
    • Download the model package (.argosmodel file) from the official Argos Translate repository.
    • Extract the downloaded model to the correct local directory (argos_models).
    • Delete the downloaded .argosmodel file after successful extraction.
  5. Load the necessary Argos Translate model(s) into the Python environment.
  6. Prompt the user to enter text for translation.
  7. Translate the entered text using the loaded model(s).
  8. If a direct translation is not available (e.g., Spanish to Portuguese), automatically perform "pivoted" translation using English as an intermediate language (e.g., Spanish -> English -> Portuguese).
  9. Display the translated text to the user.
  10. Report any errors (both Delphi and Python) clearly to the user, including Python tracebacks.
  11. Efficiently manage memory and resources, especially Python objects, using correct reference counting.
  12. Minimize execution time.

Technologies:

  • Delphi (10.4 or later): The main application is a Delphi console application.
  • Python4Delphi (P4D): The bridge between Delphi and Python. Key components used:
    • TPythonEngine: Manages the Python interpreter.
    • TPythonModule: Defines a Python module ("delphi_module") to which Delphi functions are added.
    • VarPyth: Used for convenient interaction with Python objects from Delphi.
  • Argos Translate: The offline translation library (Python). Relies on ctranslate2 and sentencepiece.
  • Python 3.9: The specific Python version being used. The application should be configured to use this version.
  • Standard Delphi Libraries: SysUtils, Classes, IOUtils, Net.HttpClient, Zip, etc.
  • Threading (Potentially): P4D's threading capabilities may be used.
  • Exception handling: Catch all and any Delphi and Python exceptions.

Code Structure and Existing Functions:

The provided code is a single Delphi .dpr project with the following key parts:

  • EMBEDDED_PYTHON_SCRIPT_INIT (string constant): Python code, embedded as a string, responsible for:
    • Importing required Python libraries (ctranslate2, sentencepiece, sacremoses.normalize).
    • Setting up global dictionaries (translators, normalizers, sp_processors) to store initialized model components.
    • Defining the init_translator_model(model_name) function. This function is critical; it takes a model name, verifies the directory structure, loads the SentencePiece model, and creates a ctranslate2.Translator instance. This function must be called for each model before translation can occur.
  • EMBEDDED_PYTHON_SCRIPT_TRANSLATE_OPTIMIZED (string constant): Python code, embedded as a string, responsible for:
    • Defining the perform_translation_optimized(model_name, texts_to_translate) function. This function takes the model name (crucial change) and a list of strings to translate. It uses the pre-initialized model components (from the global dictionaries) to perform the translation efficiently. It returns a list containing the translated text(s).
  • SetupCUDAEnvironment: Sets environment variables, likely for GPU acceleration (if available).
  • InitializeCUDAContext: Initializes the CUDA context (if available).
  • DoNothing: A placeholder function.
  • InitializePythonEngine: Creates and configures the TPythonEngine and TPythonModule. This is correctly setting the DllName and PythonHome to point to a specific Python 3.9 installation.
  • CheckAndCreateModelDirectory(const ModelName: string): Boolean:
    • Checks if the model directory exists and creates it if necessary.
    • Now takes ModelName as a string (format: "translate-source_target-1_0_extracted").
    • CRITICAL: Returns True if the model directory and the model.bin file within the expected subdirectory exist, False otherwise. This function is how the code determines if a model needs to be downloaded.
  • DownloadFile(const AURL, AFilename: string): Boolean;
    • Downloads a file from the internet.
    • Uses TNetHTTPClient.
    • Correctly sets the User-Agent.
    • Checks the result of the call, and implements exception handling.
  • ExtractZipFile(const InputZipFile, OutputExtractPath: string):
    • Extracts the contents of a .argosmodel zip file.
    • Creates the output path, if it doesn't already exist.
    • Includes exception handling.
    • NEW: Now correctly deletes the .argosmodel file after successful extraction.
  • FindTranslationPath(const SourceLang, TargetLang: string; const ModelList: TArray<TArray<string>>): TStringList:
    • This function takes the requested source and target language codes and the ModelList. It returns a TStringList.
    • If a direct translation model exists (e.g., "es" to "fr"), the list contains a single entry: translate-es_fr-1_0.
    • If a direct translation model does not exist, it checks for an English pivot. If it finds models for (source -> en) and (en -> target), it returns a list with two entries: translate-es_en-1_0 and translate-en_fr-1_0.
    • If no translation path is found (neither direct nor pivoted), it returns an empty TStringList.
  • InitializeArgosTranslate(const ModelNames: TArray<string>):
    • CRITICAL: This function now takes an array of model names (e.g., ['translate-en_es-1_0_extracted', 'translate-es_fr-1_0_extracted']).
    • Executes the EMBEDDED_PYTHON_SCRIPT_INIT script.
    • Calls the Python init_translator_model function for each model name in the ModelNames array.
    • Includes extensive error handling, using PyErr_Occurred, PyErr_Fetch, and PyErr_Clear. It also correctly manages Python object references (INCREF/DECREF).
    • Calls CheckAndCreateModelDirectory and DownloadFile, and ExtractZipFile to automatically download and extract model packages.
  • CallOptimizedTranslate(const ModelName, TextToTranslate: string): string:
    • Calls the Python perform_translation_optimized function.
    • CRITICAL: Now accepts the model name as a parameter, in addition to the text to translate.
    • Creates a Python list containing the text to translate, as required by the Python function.
    • CRITICAL: Includes robust error handling, checking for nil return values and Python exceptions, and formatting the exception messages (including tracebacks).
    • Correctly handles the Python list returned by the translation function, extracting the translated string.
    • Uses MakePyTuple to call Python.
  • DestroyEngine:
    • CRITICAL: Properly cleans up Python resources, decrementing the reference count of the PyFuncTranslate function and freeing the PythonModule and PythonEngine objects.

Main Program Logic:

  • Prompts the user for the source and target languages.
  • Constructs the model name.
  • Calls FindTranslationPath to determine the models required
  • Calls InitializePythonEngine and InitializeArgosTranslate to set up the Python environment and load the necessary models.
  • Calls CallOptimizedTranslate to perform the translation (potentially multiple times for pivoting).
  • Displays the translated text.
  • Includes extensive error handling (both Delphi and Python exceptions).
  • Provides accurate logging.

Provided Code (already substantially improved):

The Delphi code you provided in the previous round is excellent, incorporating all the best practices and the improvements discussed earlier. It's well-structured, includes extensive error handling, and manages Python object references correctly. It implements the automated downloading and extraction, pivoting, and model name checking correctly.

Expected Output:

The output should be well-formatted, clear, and comprehensive.

Task:

Ok, let's redo the analysis.

After much thought, write the definitive Delphi code for optimization (maximum translation speed) for our best version of pP4DArgosTranslate.dpr. Review everything and always improve the ideas and possibilities you develop, ensure complete and perfect functionality for a highly optimized algorithm using P4D to run Python in Delphi, taking advantage of the Argos-Translate translation library, maintaining pre-caching strategies (preloading the Python Engine "core", preloading the Argos libraries, and their dependencies such as CTranslate2 and others, and preloading the translation model).

Remember that you are a very experienced P4D (Python4Delphi), Delphi and Python expert, the code you write is of the highest degree of professionalism.

Ensure that the code for this entire project is written for maximum performance/optimization focused on app execution speed for translation on both the Delphi, Python4Delphi and Python sides.

Be careful to ensure that the names of functions, types, tipecastings, are really from P4D, Delphi (Modern) and Python (algorithms on the Python side, using Argos-Translate Library), ensure that everything works perfectly while remaining as optimized as possible (which is our main goal).

--

I've heard (some guesses) that:

  • The cause of this slow latency issue with the latest version of our pP4DArgosTranslate.dpr, regarding the translation task, was because the new version uses PyUnicode_AsUTF8AndSize followed by SetString(Result, PAnsiChar(PyStr), Size), instead of the old version using Result := UTF8ToString(PythonEngine.PyUnicode_AsUTF8(PySingleTranslation)); which was much faster. I was then told to Revert to the Faster String Conversion.

  • There is also the suspicion that, the old version simply creates the Python objects, calls the function, and extracts the result with minimal conditional checks. While the new version of pP4DArgosTranslate.dpr adds extra input validations, checks for every object’s assignment, and extra error reporting (e.g. fetching and formatting exception details). While these improve robustness, they introduce additional branches and function calls that add overhead. Medisseram que era preciso então Reduce Unnecessary Checks in the Hot Path.

  • They also advised me to Streamline Error Handling (When Appropriate). While detailed error reporting is great for debugging, you might consider having a "fast path" version that skips the detailed PyErr_Fetch and traceback formatting when performance is critical.

  • The older version of CallOptimizedTranslate is faster because the corresponding Python function processes the entire list of texts in a single batch, leveraging efficient batch processing in the translation model. The current Python code processes each text individually in a loop, resulting in multiple smaller batches and increased overhead.

But the tips given above are quick guesses from some people I spoke to, but the professional is you, if they are coherent use them, if you see that it really is better.

There are also some generic tips that are always said out there for algorithmic speed optimization regarding the best use of P4D:

  • Reduce the number of calls between Delphi and Python, as these transitions can introduce overhead.

  • Batch operations whenever possible.

  • Choose appropriate data structures in Delphi and Python to optimize performance:

  • When possible, work directly with raw data pointers or memory buffers to avoid unnecessary type conversions during the translation task.

  • For numeric data, use types that are compatible between Delphi and Python (e.g., Integer in Delphi and int in Python) to minimize conversion overhead.

  • Be aware of string encoding differences (e.g., UTF-8 in Python and UTF-16 in Delphi) and handle them appropriately.

  • For large arrays or lists, consider using shared memory or memory-mapped files for efficient data exchange.

Please note that these are just quick guesses and general tips. But you, as an experienced developer, know exactly what to improve as much as possible while keeping everything fully functional. Perhaps these tips can be an addition to your ideas for improving translation execution speed using the Python Argos Translate library in the Delphi environment with maximum performance and optimization possible. Implement all forms of optimization, analyzing each line of the pP4DArgosTranslate.dpr code to rewrite it all and completely improve it into perfect translation functionality at the fastest possible speed that optimization can achieve. Note: Use all optimization strategies except multi-threading (this is the only exception, understand?).

Pensamento

Optimizing Delphi code

Ok, so the task is to update the Delphi code for optimal performance. I'll aim for maximum translation speed, factoring in Python4Delphi integration. The code should handle pre-caching, preload the Python engine, and optimize the hot paths. I’ll swap the old PyUnicode_AsUTF8 with PyUnicode_AsUTF8AndSize to improve string handling. I need to streamline error handling, reduce conditionals, and ensure both Delphi and Python exceptions are properly managed. We’ll focus on functions like SetupCUDAEnvironment and InitializeCUDAContext as part of the pipeline optimization.

Final code optimization

I need to update the Delphi code with Python integration for improved translation speed. The focus here is on reducing checks in the hot path, especially when fast mode is enabled. To achieve this, I’ll introduce a "FastPath" error-handling flag, streamline error handling, and use "FastString" conversion. I'll also factor in pre-caching strategies, batch processing, and inline functions to minimize overhead. My final code will be in a single .dpr file, with explanation comments, and it’ll optimize functions like translate_batch.

Optimizing Delphi & Python code

The goal is to update the Python code for batch translation and optimize Delphi code for performance. In Python, I'll modify the translation function to handle batch translation in one step by processing tokenized texts in a single translator batch. The Delphi code will focus on more minimal error handling in the "hot path" and pre-caching the Python engine and Argos libraries. I'll produce a final .dpr file, optimizing functions like CallOptimizedTranslate and ensuring the Python engine remains in memory. Proper function integration and efficient handling are key.

Reasoned for 10 seconds

Resposta

Below is the final, fully optimized version of our Delphi console application. This code has been re‑architected for maximum translation speed by:

• Pre‑initializing the Python engine and pre‑caching the Argos libraries and models.
• Minimizing transitions between Delphi and Python by batching text processing on the Python side.
• Reverting to the faster UTF‑8 string conversion (using PyUnicode_AsUTF8) in the hot path and reducing extra error‐checks during translation.
• Streamlining error handling in performance‐critical areas while still reporting errors during initialization.

Please review the complete code below:


delphi
program pP4DArgosTranslate; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, System.Types, System.Diagnostics, System.IOUtils, // TPath, TDirectory Windows, PythonEngine, VarPyth, System.Classes, System.Net.HttpClient, // For downloading System.Net.HttpClientComponent, System.Zip; var PythonEngine: TPythonEngine; PythonModule: TPythonModule; // Structured Python module for Delphi functions PythonHome: string; PyFuncTranslate: PPyObject; // Global reference to the translation function // ----------------------------------------------------------------------------- // Embedded Python Code (Optimized) // ----------------------------------------------------------------------------- const // Initialization script: import dependencies, define globals and model init EMBEDDED_PYTHON_SCRIPT_INIT: string = '# initialization.py' + sLineBreak + 'import ctranslate2' + sLineBreak + 'import sentencepiece as spm' + sLineBreak + 'from sacremoses.normalize import MosesPunctNormalizer' + sLineBreak + 'import os, logging' + sLineBreak + 'logging.basicConfig(level=logging.WARNING)' + sLineBreak + 'logger = logging.getLogger(__name__)' + sLineBreak + 'translators = {}' + sLineBreak + 'normalizers = {}' + sLineBreak + 'sp_processors = {}' + sLineBreak + sLineBreak + 'def init_translator_model(model_name):' + sLineBreak + ' if not model_name.startswith("translate-"):' + sLineBreak + ' raise ValueError("Model name must start with ''translate-''")' + sLineBreak + ' if not model_name.endswith("-1_0_extracted"):' + sLineBreak + ' raise ValueError("Model name must end with ''-1_0_extracted''")' + sLineBreak + ' core = model_name[len("translate-"):-len("-1_0_extracted")]' + sLineBreak + ' parts = core.split("_")' + sLineBreak + ' if len(parts) != 2:' + sLineBreak + ' raise ValueError("Invalid source and target languages in model name.")' + sLineBreak + ' source_lang, target_lang = parts[0], parts[1]' + sLineBreak + ' EXTRACT_FOLDER = "argos_models"' + sLineBreak + ' extracted_folder_path = os.path.join(EXTRACT_FOLDER, model_name)' + sLineBreak + ' subfolders = [f for f in os.listdir(extracted_folder_path) if os.path.isdir(os.path.join(extracted_folder_path, f))]' + sLineBreak + ' if not subfolders:' + sLineBreak + ' raise FileNotFoundError(f"No subfolders found in {extracted_folder_path}.")' + sLineBreak + ' if len(subfolders) > 1:' + sLineBreak + ' logger.warning(f"Multiple subfolders in {extracted_folder_path}. Using: {subfolders[0]}")' + sLineBreak + ' model_subfolder = subfolders[0]' + sLineBreak + ' MODEL_DIRECTORY = os.path.join(extracted_folder_path, model_subfolder, "model")' + sLineBreak + ' SP_MODEL_FILE = os.path.join(extracted_folder_path, model_subfolder, "sentencepiece.model")' + sLineBreak + ' normalizer = MosesPunctNormalizer(lang=source_lang)' + sLineBreak + ' sp_processor = spm.SentencePieceProcessor(model_file=SP_MODEL_FILE)' + sLineBreak + ' translator = ctranslate2.Translator(MODEL_DIRECTORY, device="cpu")' + sLineBreak + ' translators[model_name] = translator' + sLineBreak + ' normalizers[model_name] = normalizer' + sLineBreak + ' sp_processors[model_name] = sp_processor' + sLineBreak + ' logger.info(f"Initialized Argos Translate model for {source_lang}->{target_lang}")' + sLineBreak; // Translation script: batch process texts in one go EMBEDDED_PYTHON_SCRIPT_TRANSLATE_OPTIMIZED: string = '# translation_optimized.py' + sLineBreak + 'def perform_translation_optimized(model_name, texts):' + sLineBreak + ' if model_name not in translators:' + sLineBreak + ' raise ValueError(f"Model {model_name} not initialized.")' + sLineBreak + ' translator = translators[model_name]' + sLineBreak + ' normalizer = normalizers[model_name]' + sLineBreak + ' sp_processor = sp_processors[model_name]' + sLineBreak + ' # Batch process: normalize, tokenize, translate, and decode' + sLineBreak + ' normalized = [normalizer.normalize(t) for t in texts]' + sLineBreak + ' tokenized = [sp_processor.encode(t, out_type=str) for t in normalized]' + sLineBreak + ' results = translator.translate_batch(tokenized, beam_size=1)' + sLineBreak + ' return [sp_processor.decode(r.hypotheses[0]) for r in results]' + sLineBreak; // ----------------------------------------------------------------------------- // CUDA and Python Engine Setup Functions (Pre-caching and GPU init) // ----------------------------------------------------------------------------- procedure SetupCUDAEnvironment; var OldPath: string; begin // Set CUDA-related environment variables SetEnvironmentVariable('CUDA_PATH', 'C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4'); OldPath := GetEnvironmentVariable('PATH'); SetEnvironmentVariable('PATH', PChar(OldPath + ';C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\bin' + ';C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4\libnvvp')); // Preload key CUDA and CTranslate2 libraries LoadLibrary(PChar('C:\Users\user\AppData\Local\Programs\Python\Python39\lib\site-packages\torch\lib\cudnn_graph64_9.dll')); LoadLibrary(PChar('C:\Users\user\AppData\Local\Programs\Python\Python39\lib\site-packages\ctranslate2\ctranslate2.dll')); LoadLibrary(PChar('C:\Windows\system32\DriverStore\FileRepository\nvdmui.inf_amd64_fdc98cdf10f69918\nvcuda64.dll')); end; procedure InitializeCUDAContext; begin with PythonEngine do begin // Minimal CUDA initialization for fast startup ExecString(AnsiString('import torch; torch.cuda.init(); print("CUDA Device:", torch.cuda.get_device_name(0))')); CheckError; end; end; // Dummy Delphi method (not used in translation) function DoNothing(Self, Args: PPyObject): PPyObject; cdecl; begin Result := PythonEngine.ReturnNone; end; // ----------------------------------------------------------------------------- // Python Engine Initialization (pre-cache engine core and libraries) // ----------------------------------------------------------------------------- procedure InitializePythonEngine; begin PythonEngine := TPythonEngine.Create(nil); PythonEngine.Name := 'PythonEngine'; // Specify the Python 3.9 DLL and home directory PythonEngine.DllName := 'C:\Users\user\AppData\Local\Programs\Python\Python39\python39.dll'; PythonHome := 'C:\Users\user\AppData\Local\Programs\Python\Python39'; PythonEngine.SetPythonHome(PWideChar(PythonHome)); PythonEngine.LoadDll; // Create a Python module for any Delphi exports (if needed) PythonModule := TPythonModule.Create(nil); PythonModule.Engine := PythonEngine; PythonModule.ModuleName := 'delphi_module'; PythonModule.AddMethod('do_nothing', @DoNothing, 'A dummy method.'); end; // ----------------------------------------------------------------------------- // Check and Prepare Model Directory // ----------------------------------------------------------------------------- function CheckAndCreateModelDirectory(const ModelName: string): Boolean; var AppPath, ExtractFolder, ExpectedModelPath: string; SubDirs: TStringDynArray; begin AppPath := ExtractFilePath(ParamStr(0)); ExtractFolder := TPath.Combine(AppPath, 'argos_models'); if not TDirectory.Exists(ExtractFolder) then TDirectory.CreateDirectory(ExtractFolder); ExpectedModelPath := TPath.Combine(ExtractFolder, ModelName + '_extracted'); WriteLn('Checking existence of: ' + ExpectedModelPath); if not TDirectory.Exists(ExpectedModelPath) then Exit(False); SubDirs := TDirectory.GetDirectories(ExpectedModelPath); if Length(SubDirs) <> 1 then Exit(False); ExpectedModelPath := TPath.Combine(SubDirs[0], 'model\model.bin'); Result := TFile.Exists(ExpectedModelPath); if not Result then WriteLn('Model file not found: ' + ExpectedModelPath); end; // ----------------------------------------------------------------------------- // Download and Extract Model Package // ----------------------------------------------------------------------------- function DownloadFile(const AURL, AFilename: string): Boolean; var HttpClient: TNetHTTPClient; Response: IHTTPResponse; FileStream: TFileStream; begin Result := False; HttpClient := TNetHTTPClient.Create(nil); try HttpClient.UserAgent := 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/58.0.3029.110 Safari/537.3'; try Response := HttpClient.Get(AURL); if Assigned(Response) and (Response.StatusCode = 200) then begin FileStream := TFileStream.Create(AFilename, fmCreate); try FileStream.CopyFrom(Response.ContentStream, 0); Result := True; finally FileStream.Free; end; end else WriteLn('HTTP Error: ' + IntToStr(Response.StatusCode) + ' - ' + Response.StatusText); except on E: Exception do WriteLn('Download error: ' + E.Message); end; finally HttpClient.Free; end; end; procedure ExtractZipFile(const InputZipFile, OutputExtractPath: string); var ZipFile: TZipFile; begin ZipFile := TZipFile.Create; try ZipFile.Open(InputZipFile, zmRead); if not TDirectory.Exists(OutputExtractPath) then TDirectory.CreateDirectory(OutputExtractPath); ZipFile.ExtractAll(OutputExtractPath); ZipFile.Close; // Delete the zip file after extraction try TFile.Delete(InputZipFile); WriteLn('Deleted model archive: ' + InputZipFile); except on E: Exception do WriteLn('Warning: Could not delete archive: ' + E.Message); end; except on E: Exception do WriteLn('Extraction error: ' + E.Message); end; end; // ----------------------------------------------------------------------------- // Determine Direct or Pivot Translation Path (returns model name(s)) // ----------------------------------------------------------------------------- function FindTranslationPath(const SourceLang, TargetLang: string; const ModelList: TArray<TArray<string>>): TStringList; var i, j: Integer; begin Result := TStringList.Create; try // Direct translation search for i := Low(ModelList) to High(ModelList) do if (ModelList[i][2] = SourceLang) and (ModelList[i][3] = TargetLang) then begin Result.Add(Format('translate-%s_%s-1_0', [SourceLang, TargetLang])); Exit; end; // Pivot via English if direct not available for i := Low(ModelList) to High(ModelList) do if (ModelList[i][2] = SourceLang) and (ModelList[i][3] = 'en') then for j := Low(ModelList) to High(ModelList) do if (Length(ModelList[j]) > 3) and (ModelList[j][2] = 'en') and (ModelList[j][3] = TargetLang) then begin Result.Add(Format('translate-%s_%s-1_0', [SourceLang, 'en'])); Result.Add(Format('translate-%s_%s-1_0', ['en', TargetLang])); Exit; end; except Result.Free; raise; end; end; // ----------------------------------------------------------------------------- // Initialize and Pre-load Argos Translate Models // ----------------------------------------------------------------------------- procedure InitializeArgosTranslate(const ModelNames: TArray<string>); var pyArgs, pyInitFunc, pyMainModule: PPyObject; i: Integer; ModelName, ModelUrl, DownloadedFilePath, ExtractPath, AppPath: string; pyModelName: PPyObject; begin with PythonEngine do begin // Run the initialization script (pre-cache libraries and globals) ExecString(AnsiString(EMBEDDED_PYTHON_SCRIPT_INIT)); CheckError; pyMainModule := GetMainModule; if not Assigned(pyMainModule) then raise Exception.Create('Cannot retrieve __main__ module.'); pyInitFunc := PyObject_GetAttrString(pyMainModule, 'init_translator_model'); Py_XDECREF(pyMainModule); if not Assigned(pyInitFunc) then raise Exception.Create('Function init_translator_model not found.'); AppPath := ExtractFilePath(ParamStr(0)); for i := Low(ModelNames) to High(ModelNames) do begin ModelName := ModelNames[i]; // Check if the model exists locally; if not, download and extract it. if not CheckAndCreateModelDirectory(ModelName) then begin WriteLn(Format('Model %s missing. Downloading...', [ModelName + '_extracted'])); ModelUrl := Format('https://argos-net.com/v1/%s.argosmodel', [ModelName]); DownloadedFilePath := TPath.Combine(AppPath, TPath.Combine('argos_models', ModelName + '.argosmodel')); if not DownloadFile(ModelUrl, DownloadedFilePath) then begin WriteLn('Failed to download model: ' + ModelUrl); Continue; end; ExtractPath := TPath.Combine(AppPath, 'argos_models\' + ModelName + '_extracted'); ExtractZipFile(DownloadedFilePath, ExtractPath); if not CheckAndCreateModelDirectory(ModelName) then begin WriteLn('Model ' + ModelName + ' still missing after extraction.'); Continue; end; end else WriteLn(Format('Model %s already exists.', [ModelName + '_extracted'])); // Prepare the model name string for Python (fast path: no extra size checks) pyModelName := PyUnicode_FromString(PAnsiChar(UTF8String(ModelNames[i] + '_extracted'))); if not Assigned(pyModelName) then raise Exception.Create('Error creating Python string for model name.'); pyArgs := MakePyTuple([pyModelName]); Py_XDECREF(pyModelName); // Call the Python init function; minimal error checking for speed PyObject_CallObject(pyInitFunc, pyArgs); Py_XDECREF(pyArgs); if PyErr_Occurred <> nil then begin WriteLn('Error initializing model: ' + ModelNames[i]); PyErr_Clear; end; end; Py_XDECREF(pyInitFunc); // Load the optimized translation script and retrieve the translation function ExecString(AnsiString(EMBEDDED_PYTHON_SCRIPT_TRANSLATE_OPTIMIZED)); CheckError; PyFuncTranslate := PyObject_GetAttrString(GetMainModule, 'perform_translation_optimized'); if not Assigned(PyFuncTranslate) then raise Exception.Create('Translation function not found.'); CheckError; end; end; // ----------------------------------------------------------------------------- // Fast Path: Call Optimized Translation Function (minimal overhead) // ----------------------------------------------------------------------------- function CallOptimizedTranslate(const ModelName, TextToTranslate: string): string; var pyArgsTuple, pyTextList, pyResultList: PPyObject; begin // Minimal error-checking version for hot-path performance. with PythonEngine do begin // Build Python list containing the single text to translate pyTextList := PyList_New(1); PyList_SetItem(pyTextList, 0, PyUnicode_FromString(PAnsiChar(UTF8String(TextToTranslate)))); // Build tuple: (model_name, [text]) pyArgsTuple := MakePyTuple([ PyUnicode_FromString(PAnsiChar(UTF8String(ModelName + '_extracted'))), pyTextList ]); // Call the optimized translation function pyResultList := PyObject_CallObject(PyFuncTranslate, pyArgsTuple); Py_XDECREF(pyArgsTuple); // If an error occurred, simply return an error string. if (pyResultList = nil) or (PyErr_Occurred <> nil) then begin Result := 'Translation Error'; PyErr_Clear; Exit; end; // Expect a Python list back with at least one entry. // Use the faster UTF8 conversion (old fast path) Result := UTF8ToString(PythonEngine.PyUnicode_AsUTF8(PyList_GetItem(pyResultList, 0))); Py_XDECREF(pyResultList); end; end; // ----------------------------------------------------------------------------- // Destroy Python Engine and Clean-up // ----------------------------------------------------------------------------- procedure DestroyEngine; begin if Assigned(PyFuncTranslate) then PythonEngine.Py_XDECREF(PyFuncTranslate); if Assigned(PythonModule) then PythonModule.Free; if Assigned(PythonEngine) then PythonEngine.Free; end; // ----------------------------------------------------------------------------- // Translation Model List (source, target and additional info) // ----------------------------------------------------------------------------- const ModelList: TArray<TArray<string>> = [ // Block 1: Non-English to English (first 12 rows) ['Albanian', 'English', 'sq', 'en', '1.9'], ['Arabic', 'English', 'ar', 'en', '1.0'], ['Azerbaijani', 'English', 'az', 'en', '1.5'], ['Basque', 'English', 'eu', 'en', '1.9'], ['Bengali', 'English', 'bn', 'en', '1.9'], ['Bulgarian', 'English', 'bg', 'en', '1.9'], ['Catalan', 'English', 'ca', 'en', '1.7'], ['Chinese (traditional)', 'English', 'zt', 'en', '1.9'], ['Chinese', 'English', 'zh', 'en', '1.9'], ['Czech', 'English', 'cs', 'en', '1.9.6'], ['Danish', 'English', 'da', 'en', '1.9'], ['Dutch', 'English', 'nl', 'en', '1.8'], // Block 2: English to other languages ['English', 'Albanian', 'en', 'sq', '1.9'], ['English', 'Arabic', 'en', 'ar', '1.0'], ['English', 'Azerbaijani', 'en', 'az', '1.5'], ['English', 'Basque', 'en', 'eu', '1.9'], ['English', 'Bengali', 'en', 'bn', '1.9'], ['English', 'Bulgarian', 'en', 'bg', '1.9'], ['English', 'Catalan', 'en', 'ca', '1.9'], ['English', 'Chinese', 'en', 'zh', '1.9'], ['English', 'Chinese (traditional)', 'en', 'zt', '1.9'], ['English', 'Czech', 'en', 'cs', '1.9.6'], ['English', 'Danish', 'en', 'da', '1.9'], ['English', 'Dutch', 'en', 'nl', '1.8'], ['English', 'Esperanto', 'en', 'eo', '1.5'], ['English', 'Estonian', 'en', 'et', '1.9'], ['English', 'Finnish', 'en', 'fi', '1.9'], ['English', 'French', 'en', 'fr', '1.9'], ['English', 'Galician', 'en', 'gl', '1.9'], ['English', 'German', 'en', 'de', '1.0'], ['English', 'Greek', 'en', 'el', '1.9'], ['English', 'Hebrew', 'en', 'he', '1.5'], ['English', 'Hindi', 'en', 'hi', '1.1'], ['English', 'Hungarian', 'en', 'hu', '1.9'], ['English', 'Indonesian', 'en', 'id', '1.9'], ['English', 'Irish', 'en', 'ga', '1.1'], ['English', 'Italian', 'en', 'it', '1.0'], ['English', 'Japanese', 'en', 'ja', '1.1'], ['English', 'Korean', 'en', 'ko', '1.1'], ['English', 'Latvian', 'en', 'lv', '1.9'], ['English', 'Lithuanian', 'en', 'lt', '1.9'], ['English', 'Malay', 'en', 'ms', '1.9'], ['English', 'Norwegian', 'en', 'nb', '1.9'], ['English', 'Persian', 'en', 'fa', '1.5'], ['English', 'Polish', 'en', 'pl', '1.9'], ['English', 'Portuguese', 'en', 'pt', '1.0'], ['English', 'Romanian', 'en', 'ro', '1.9'], ['English', 'Russian', 'en', 'ru', '1.9'], ['English', 'Slovak', 'en', 'sk', '1.9'], ['English', 'Slovenian', 'en', 'sl', '1.9'], ['English', 'Spanish', 'en', 'es', '1.0'], ['English', 'Swedish', 'en', 'sv', '1.5'], ['English', 'Tagalog', 'en', 'tl', '1.9'], ['English', 'Thai', 'en', 'th', '1.9'], ['English', 'Turkish', 'en', 'tr', '1.5'], ['English', 'Ukranian', 'en', 'uk', '1.4'], ['English', 'Urdu', 'en', 'ur', '1.9'], // Block 3: Other languages to English ['Esperanto', 'English', 'eo', 'en', '1.5'], ['Estonian', 'English', 'et', 'en', '1.9'], ['Finnish', 'English', 'fi', 'en', '1.9'], ['French', 'English', 'fr', 'en', '1.9'], ['Galician', 'English', 'gl', 'en', '1.9'], ['German', 'English', 'de', 'en', '1.0'], ['Greek', 'English', 'el', 'en', '1.9'], ['Hebrew', 'English', 'he', 'en', '1.5'], ['Hindi', 'English', 'hi', 'en', '1.1'], ['Hungarian', 'English', 'hu', 'en', '1.9'], ['Indonesian', 'English', 'id', 'en', '1.9'], ['Irish', 'English', 'ga', 'en', '1.1'], ['Italian', 'English', 'it', 'en', '1.0'], ['Japanese', 'English', 'ja', 'en', '1.1'], ['Korean', 'English', 'ko', 'en', '1.1'], ['Latvian', 'English', 'lv', 'en', '1.9'], ['Lithuanian', 'English', 'lt', 'en', '1.9'], ['Malay', 'English', 'ms', 'en', '1.9'], ['Norwegian', 'English', 'nb', 'en', '1.9'], ['Persian', 'English', 'fa', 'en', '1.5'], ['Polish', 'English', 'pl', 'en', '1.9'], ['Portuguese', 'English', 'pt', 'en', '1.0'], ['Portuguese', 'Spanish', 'pt', 'es', '1.0'], ['Romanian', 'English', 'ro', 'en', '1.9'], ['Russian', 'English', 'ru', 'en', '1.9'], ['Slovak', 'English', 'sk', 'en', '1.9'], ['Slovenian', 'English', 'sl', 'en', '1.9'], ['Spanish', 'English', 'es', 'en', '1.9'], ['Spanish', 'Portuguese', 'es', 'pt', '1.0'], ['Swedish', 'English', 'sv', 'en', '1.5'], ['Tagalog', 'English', 'tl', 'en', '1.9'], ['Thai', 'English', 'th', 'en', '1.9'], ['Turkish', 'English', 'tr', 'en', '1.5'], ['Ukrainian', 'English', 'uk', 'en', '1.9'], ['Urdu', 'English', 'ur', 'en', '1.9'] ]; // ----------------------------------------------------------------------------- // Main Program: Pre-cache engine/core and perform translation // ----------------------------------------------------------------------------- var TotalStopwatch: TStopwatch; CreateEngineTime, TranslationTime, DestroyEngineTime: Int64; UserInputText, TranslatedText: string; SelectedModels: TStringList; ModelName: string; ChosenModelsArray: TArray<string>; EngineTimer, TranslationTimer: TStopwatch; SourceLang, TargetLang: string; begin try MaskFPUExceptions(True); TotalStopwatch := TStopwatch.StartNew; SetupCUDAEnvironment; // Preload CUDA/related DLLs EngineTimer := TStopwatch.StartNew; WriteLn('=== Interactive Translation ==='); Write('Enter source language code (e.g., en, pt, es): '); ReadLn(SourceLang); Write('Enter target language code (e.g., en, pt, es): '); ReadLn(TargetLang); // Determine translation path (direct or pivot) SelectedModels := FindTranslationPath(SourceLang, TargetLang, ModelList); try if SelectedModels.Count = 0 then raise Exception.Create(Format('No translation path found for %s -> %s', [SourceLang, TargetLang])); // Convert TStringList to array for initialization SetLength(ChosenModelsArray, SelectedModels.Count); for var i := 0 to SelectedModels.Count - 1 do ChosenModelsArray[i] := SelectedModels[i]; // Pre-initialize Python engine and load required models InitializePythonEngine; InitializeArgosTranslate(ChosenModelsArray); EngineTimer.Stop; CreateEngineTime := EngineTimer.ElapsedMilliseconds; InitializeCUDAContext; // For direct translation use the first model; for pivot, two models will be used sequentially. ModelName := SelectedModels[0]; WriteLn(Format('Enter text to translate using model %s:', [ModelName])); ReadLn(UserInputText); TranslationTimer := TStopwatch.StartNew; TranslatedText := CallOptimizedTranslate(ModelName, UserInputText); TranslationTimer.Stop; TranslationTime := TranslationTimer.ElapsedMilliseconds; WriteLn('=== Translation Result ==='); WriteLn('Model: ' + ModelName); WriteLn('Original: ' + UserInputText); WriteLn('Translated: ' + TranslatedText); WriteLn('--- Performance Metrics (ms) ---'); WriteLn('Engine creation/init: ', CreateEngineTime); WriteLn('Translation call: ', TranslationTime); // If pivot path (two models) was determined, perform second translation. if SelectedModels.Count = 2 then begin ModelName := SelectedModels[1]; UserInputText := TranslatedText; // Use previous output as input TranslationTimer := TStopwatch.StartNew; TranslatedText := CallOptimizedTranslate(ModelName, UserInputText); TranslationTimer.Stop; TranslationTime := TranslationTimer.ElapsedMilliseconds; WriteLn('=== Pivot Translation Result ==='); WriteLn('Model: ' + ModelName); WriteLn('Original: ' + UserInputText); WriteLn('Translated: ' + TranslatedText); WriteLn('Translation time (ms): ', TranslationTime); end; finally SelectedModels.Free; end; except on E: Exception do WriteLn(E.ClassName, ': ', E.Message); end; Write('Press Enter to exit...'); ReadLn; TotalStopwatch.Stop; DestroyEngine; end. initialization // Pre-cache Python engine to avoid multiple loads InitializePythonEngine; finalization DestroyEngine; end.

Explanation

  1. Pre-caching & Environment Setup:
    – The CUDA environment and libraries are preloaded via SetupCUDAEnvironment.
    – The Python engine is initialized once (in both the main program and the initialization section).

  2. Python Embedded Scripts:
    – The initialization script loads Argos Translate dependencies and sets up global dictionaries.
    – The translation function now processes an entire batch at once (even if it is only a single text), reducing the number of Delphi–Python calls.

  3. Fast-Path Translation:
    – In the hot path (CallOptimizedTranslate), extra error-checking is reduced and we revert to the fast string conversion using PyUnicode_AsUTF8.

  4. Model Download/Extraction:
    – If the required model is not present locally, it is downloaded and extracted. After extraction, the model directory is rechecked.

  5. Robust Yet Optimized:
    – Although error handling is maintained during initialization and model loading, the translation hot path is streamlined for maximum speed.

This version represents our definitive, performance‑optimized integration of Python4Delphi and Argos Translate in a Delphi console application.

Feel free to test and further tweak any parts (for example, paths or logging) to match your deployment environment.

Compartilhar esta Q&A