Skip to content
This repository has been archived by the owner on May 12, 2023. It is now read-only.

[Added Feature] Interactive Mode #43

Merged
merged 5 commits into from
Apr 23, 2023
Merged

Conversation

Kasimir123
Copy link
Contributor

I added an additional Python callback that allows main.cpp to grab user input from the Python script. This allows users to run interactive mode as if they were running the standard llama.cpp file. For testing I used the bob prompt from llama's prompts, this can be run with the following Python file:

from pyllamacpp.model import Model

def new_text_callback(text: str):
    print(text, end="", flush=True)

def grab_text_callback() -> str:
    return input() + "\n"

model = Model(ggml_model='..\\gpt4allconverted.bin', n_ctx=1024)

prompt = """
Transcript of a dialog, where the User interacts with an Assistant named Bob. Bob is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.

User: Hello, Bob.
Bob: Hello. How may I help you today?
User: Please tell me the largest city in Europe.
Bob: Sure. The largest city in Europe is Moscow, the capital of Russia.
User:"""

model.generate(prompt, n_predict=256, new_text_callback=new_text_callback, grab_text_callback=grab_text_callback, n_threads=8, interactive=True, repeat_penalty=1.0, antiprompt=["User:"])```

@abdeladim-s
Copy link
Collaborator

Thanks @Kasimir123 for the PR. Great idea!
But I think stdin should work without the need to any callback!
Have you tried it before and didn't work for you ?

@Kasimir123
Copy link
Contributor Author

Thanks @Kasimir123 for the PR. Great idea! But I think stdin should work without the need to any callback! Have you tried it before and didn't work for you ?

stdin does work if you are just trying to do a basic chatbot in a terminal. The callback allows people to use the python wrapper to build out applications using the llma model. For example now you could have a webui that can interact with the bot, or you can automatically feed it certain data as well.

src/main.cpp Outdated
// input stream is bad or EOF received
return 0;
}
line = grab_text_callback().cast<std::string>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to let the callback signal EOF then check it here so that the function can terminate gracefully?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kasimir123 Yeah You are right.
I had a similar idea but was working with a different approach. I want to have more control over the inference so I decided to re-implement the logic in a similar way.
What @nuance1979 is asking for (and many other people) is difficult to do from the callback.

@Kasimir123 let me know if you have any idea on how to signal EOF, otherwise I will merge the PR ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of two ways to signal EOF:

  1. Use a special string (e.g., "[EOF]") or character (e.g., "\x03");
  2. Let the callback return a Tuple[string, bool] where the boolean means EOF.

How do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, we could merge this PR first and continue this discussion in another PR. @Kasimir123 @abdeladim-s

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kasimir123 is not replying, so I don't know if he is still working on it.

@nuance1979, I already implemented a different logic for interactive mode. So, this approach won't be necessary any more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh great! Are you going to make a PR soon? Look forward to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Was a busy weekend. I will look into this tomorrow and see what I can come up with. Sounds like @abdeladim-s already has a different approach but I want to see if its possible just out of curiosity, will share what I find.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nuance1979 @abdeladim-s I managed to add EOF file handling by adding a check to see if the callback result was of type string. This means that if you want end of file you can return None and it will correctly exit the program. To test this change change your callback to:

def grab_text_callback() -> str:
    inpt = input()
    if inpt == "END":
        return None
    return inpt + "\n"

src/main.cpp Outdated
// input stream is bad or EOF received
py::handle x = grab_text_callback();

if (!py::isinstance<py::str>(x))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Does it mean that I can return anything other than a Python str to signal EOF?
If that's the idea, I would suggest the following:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want it to warn and exit if the type is not string or simply warn and try again for input? I personally prefer the warn and exit method as the other way could lead to some weird loops.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what string do we want the warning to be? Something like: "Input was not of type py:str"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want it to warn and exit if the type is not string or simply warn and try again for input? I personally prefer the warn and exit method as the other way could lead to some weird loops.

Well if it exits, it's not a warning anymore; it's a failure. I think failure here is a bit heavy-handed; warn and ignore is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated with the above. To test you can use:

def grab_text_callback() -> str:
    inpt = input()
    if inpt == "END":
        return None
    if inpt == "1":
        return 1
    if inpt == "dict":
        return {1: 'help'}
    return inpt + "\n"

src/main.cpp Outdated
line.pop_back(); // Remove the continue character
else if (!py::isinstance<py::str>(x))
{
new_text_callback("Input was not of type py:str");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think it's a good idea to use new_text_callback to print out warning. I'm suggesting fprintf(stderr,...) to give out the warning in a "side channel", without affecting the "main channel" at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep that warning message or have a different message? just before I do another commit changing the way we print it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the convention in this file, I would suggest:

fprintf(stderr, "%s : input was not of type py::str. will ignore.\n", __func__);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, just pushed the latest change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Can you update the README file to let users know that they should return None from grab_text_callback to signal EOF? Also I trust you've already tested it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the README with an example of how to run interactive mode as well since I saw people asking about that in the issues and a quick start will help. And yes, I have been running this on my end and recompiling and testing with the code snippet above each time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Kasimir123 and @nuance1979 for the contribution & sorry for this late reply.
I will merge it for now.

# To signal EOF, return None
if inpt == "END":
return None
return inpt + "\n"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about return inpt + "\n" instead of return inpt? Because when I test it, only return inpt works correctly. It makes sense because the original std::getline() call also returns a line without the trailing \n.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nuance1979
I think you are right, I will fix it.

def new_text_callback(text: str):
print(text, end="", flush=True)

def grab_text_callback() -> str:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

def grab_text_callback() -> Optional[str]:

@abdeladim-s abdeladim-s merged commit 8f8666d into nomic-ai:main Apr 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants