Sunday, February 02, 2025

Delphi and AI [7]: How good are local DeepSeek models (for Delphi)

Due to potential privacy concerns with DeepSeek servers (we’re unsure if the data sent over the paid API is kept private), I looked into some smaller DeepSeek models available on the Ollama.com site. These models use less complex AI with fewer parameters than the online version, but they might still be good enough for an average Delphi programmer. We’ll see.

For testing, I used a powerful RTX 4090 card with 24 GB of memory. If your graphics card has less memory, your selection of useful models will be more limited.

I asked all models the same two questions: one on a general programming topic and another specific to the FireMonkey platform. The first question was:

"I have a multiline string containing newline ASCII characters (TMemo.Text). I want to change it to a single-line string with only printable ASCII characters. I could do that with BASE64 encoding, for example. I would, however, like to keep the text as much readable as possible by "encoding" only non-printable characters. Is there a simple way to do that?"

You can check Codellama’s response in an older post (Codellama being the only local model I had tested so far): Delphi and AI [5]: Encoding Multi-line Strings.

Last week, I asked the same question to the online DeepSeek-Reasoning model. Check the answers in this post: Delphi and AI [6]: DeepSeek-Reasoning Model.

The second question was:

"How can I copy text to clipboard in a Delphi Firemonkey application?"

Read here for Codellama’s response: Delphi and AI [4]: Device-ndependent clipboard.

The answer from the online DeepSeek-Reasoning model can be found at the end of this post.

Let’s see how the models are performing! As always, full logs are available on GitHub.

[deepseek-coder-v2]

DeepSeek-coder-v2 is the newest coding-specific refinement of the DeepSeek model. It was updated 4 months ago and offers two versions: with 16b and 236b parameters. As I have no chance of running the latter locally, I tested the 16 billing parameter version. It uses roughly 13 GB of graphics memory.

The first aswer was:

function EncodeNonPrintable(const S: string): string;
var
  I: Integer;
begin
  Result := '';
  for I := 1 to Length(S) do
  begin
    case Ord(S[I]) of
      0..31, 127: // Non-printable ASCII characters (including control codes and DEL)
        Result := Result + Format('\x%2.2X', [Ord(S[I])]);
      else
        Result := Result + S[I];
    end;
  end;
end;

As we've seen before, this is a classical AI approach that almost works. The only problem is that the character '\' is not encoded. Easy to fix, but it is on you to find the problem and implement the fix.

The answer to the second question was:

uses
  System.SysUtils, FMX.Clipboard, FMX.Forms;

procedure
TForm1.CopyToClipboardButtonClick(Sender: TObject);
var
  ClipboardService: IFMXClipboardService;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(ClipboardService)) then
  begin
    ClipboardService.SetText('Your text to copy');
    ShowMessage('Text copied to clipboard!');
  end
  else
  begin
    ShowMessage('Clipboard service not available on this platform.');
  end;
end;

Close, but not good enough. AI forgot to use unit FMX.Platform and used a non-existing method SetText instead of a SetClipboard. Otherwise, not a bad attempt. 

[deepseek-coder:33b]

I had also tested an older deepseek-coder model. As it offers a 33b version (in addition to 1.3b and 6.7b) which just fits onto my graphics card (it used roughly 22 GB of memory) I wanted to know if how it would compare with a newer but smaller model.

Solution of the "encoding" question is interesting as it preallocates a larger string and truncates it at the end:

function EncodeNonPrintables(const InputString: String): String;
var
  i, j: Integer;
begin
  SetLength(Result, Length(InputString) * 2); // Worst case scenario

  j :=
0;
  for i := 1 to Length(InputString) do begin
    if (Ord(InputString[i]) < 32) or (Ord(InputString[i]) > 126) then begin
      // Non-printable character, replace it with '\x' followed by its hex value.
      Result[j] := Format('\x%2.2X', [Ord(InputString[i])]);
      Inc(j, 4);
    end else begin
      // Printable character, copy as is.
      Result[j] := InputString[i];
      Inc(j);
    end;
  end;

  SetLength(Result, j); // Trim the result string to its actual length.
end;

It still forgets to encode the '\' character, though. Additionally, the preallocation should be larger by a factor of 4, not two.

Let's see how it handles copying to the clipboard:

uses FMX.Platform; 

procedure
CopyToClipboard(const Text: string);
var
  ClipboardService: IFMXClipboardService;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, IInterface(ClipboardService)) then
    ClipboardService.SetClipboard(Text);
end;

Nice!

[deepseek-r1-14b]

I've also tested two variations of the deepseek-reasoning model, last update 11 days ago. From the long list of available versions (from 1.5b to 671b parameters) I picked 14b and 32b variations.

function ConvertToSingleLine(const S: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 0 to Length(S) - 1 do
  begin
    case S[i] of
      #10, #13:
        // Replace newlines with spaces
        Result += ' ';
      #9, #11, #12: // Other control characters like tab (9), vertical tab (11), form feed (12)
        Result += '?';
    else
      if S[i] < #32 or S[i] > #126 then
        // Replace other non-printable ASCII characters with '?'
        Result += '?'
      else
        // Keep printable characters as they are
        Result += S[i];
    end;
  end;
end;

Dear AI, I don't know how to tell you, but that is not a Delphi code.

What about the Firemonkey and clipboard?

uses
  FMX.Clipboard; 

procedure
TForm1.Button1Click(Sender: TObject);
begin
  // Access the clipboard through the Application's Clipboard property
  TPlatform.Current.Clipboard.SetText('Your desired text here');
end;

Yeah, this also doesn't work.

[deepseek-r1-32b]

What about the larger brother with 32b parameters? This one used almost full 24 GB of graphics memory. Did this improve the model?

Not really. The first answer is again not written in Delphi (plus it doesn't encode the '\' character):

function MakeSingleLine(const S: string): string;
var
  i: Integer;
  c: Char;
begin
  Result := '';
  for i := 1 to Length(S) do
  begin
    c := S[i];
    if (c >= #32) and (c <= #126) then
      Result += c
    else
      Result += Format('\x%0.2x', [Ord(c)]);
  end;
end;

The second answer also fails completely:

uses
  System.Clipboard;

procedure
CopyTextToClipboard(const AText: string);
begin
  try
    TClipboard.SetText(AText);
  except
    // Handle any exceptions (e.g., platform-specific errors)
    on E: Exception do
      ShowMessage('Error copying text to clipboard: ' + E.Message);
  end;
end;

[deepseek-reasoning]

We've submitted the first question to the online deepseek-reasoning before and it performed admirably. But how does it handle the Firemonkey clipboard question?

Not very well:

uses
  FMX.Clipboard; // Include the clipboard unit

procedure
TForm1.CopyTextToClipboard(const AText: string);
begin
  if Assigned(TClipboard.Default) then
    TClipboard.Default.SetText(AText);
end;

Interestingly, if we examine the 'reasoning' part of the answer we can find this fragment inside:

var
  ClipboardService: IFMXClipboardService;
begin
  if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, ClipboardService) then
    ClipboardService.SetClipboard('My text');
end;

The AI has started thinking in the correct direction but then tried to simplify the answer and arrived at the incorrect conclusion.

Recap

The most useful local model - for Delphi programmers - seems to be deepseek-coder-v2. It works reasonably well and uses "only" 13 GB of a graphics memory. Because of that I'll from now on include it in my AI testing.

1 comment:

  1. I've tried mistral:7b-instruct-v0.3-fp16 from Ollama big list of Mistral models, for Delphi it seems quite good with Delphi. Here's its answer to your question:

    Yes, you can create a function that replaces non-printable ASCII characters with their corresponding hexadecimal
    representations, while keeping newline characters intact. Here's an example in Delphi:

    ```delphi
    function SafeEncode(const S: string): string;
    var
    I: Integer;
    begin
    Result := '';
    for I := 1 to Length(S) do
    if (S[I] < #32) or (S[I] > #126) then
    Result := Result + '\\x' + IntToHex(Ord(S[I]), 2)
    else
    Result := Result + S[I];
    end;
    ```

    You can call this function on your TMemo.Text before saving it:

    ```delphi
    TMemo1.Lines.SaveToFile('example.txt'); // Save original multiline text
    TMemo1.Text := SafeEncode(TMemo1.Text); // Encode non-printable characters
    TMemo1.Lines.SaveToFile('example_encoded.txt'); // Save encoded text
    ```

    When you need to decode the text, you can create a corresponding function:

    ```delphi
    function SafeDecode(const S: string): string;
    var
    I: Integer;
    begin
    Result := '';
    for I := 1 to Length(S) do
    if S[I] = #34 then // Handle backslashes and quotes properly
    if S[I + 1] = 'x' then
    begin
    Inc(I);
    Result := Result + StrToCodePoint(IntHexStrToBin2(Copy(S, I, 6))) - #128;
    Inc(I, 6);
    end
    else if (S[I] >= #32) and (S[I] <= #126) then
    Result := Result + S[I];
    end;
    ```

    You can decode the text when you need to display it:

    ```delphi
    TMemo1.Text := SafeDecode(FileReadAllText('example_encoded.txt')); // Decode encoded text and assign to
    TMemo1.Text
    ```

    These functions replace characters outside of the ASCII printable range (32-126) with their hexadecimal
    representation, while keeping newline characters as is. Non-printable ASCII characters will look like `\xHH`,
    where HH represents the hexadecimal value of the character.

    ReplyDelete