You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hello! Just wanted to share some code (WIP) I wrote to capture the output of e.g. llama_print_timings, mainly because I'm doing some testing on different models and thought it was useful to have:
Cleaner output using the logging module, but still have the llama.cpp output available
Capture the output of the model timing information for later analysis
As such, I didn't want to turn off "verbose", so this code will work as a standalone drop-in context manager, compatible with llama-cpp-python if you create the model with verbose=True. It could also be implemented somewhat trivially into the package code, but right now doesn't support /dev/null (but does support dropping the output).
Caveats
Output from the logging module is not redirected, I'm not entirely sure why because there's no reference to sys.__stderr__ which would indicate it uses the original stderr stream (therefore bypassing redirects).
Stream content is only written to the buffers on leaving the context (via __exit__), this is due to using a temporary file. I can think of a few ways to avoid this, maybe the easiest would be to create simple wrapper classes for TemporaryFile and TextIOWrapper so that on write* methods it would simultaneously write to the buffer (also removing the need for subsequent flush(), seek() and read() calls. It should work but may introduce issues with threads or if a C stream and a python stream are written to at the same time (would require testing).
Tested on python 3.11 / Windows 11. When I have a moment, I'll test it in WSL/Linux and I know someone with a Mac I can ask later as well.
TODO
One final note, I'll probably refactor it a little so it is a bit more readable and efficient, specifically:
Rewriting (essentially all) duplicated code
Allowing redirection of only one of the streams as opposed to both
Proper error handling
At the very least revert the stdout/stderr streams on exception.
"""This module contains the io class for storing stdout and stderr.Thanks to:- https://github.com/abetlen/llama-cpp-python/blob/fcdf337d84591c46c0a26b8660b2e537af1fd353/llama_cpp/_utils.py- https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/- https://stackoverflow.com/questions/17942874/stdout-redirection-with-ctypesMIT LicenseCopyright (c) 2024 zeyus (https://github.com/zeyus)Copyright (c) 2023 Andrei BetlenPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."""importctypesimportosimportsysimporttempfilefromioimportSEEK_SET, StringIO, TextIOWrapperWIN: bool=os.name=="nt"ifnotWIN:
libc=ctypes.CDLL(None)
c_stdout=ctypes.c_void_p.in_dll(libc, "stdout")
c_stderr=ctypes.c_void_p.in_dll(libc, "stderr")
else:
ifhasattr(sys, "gettotalrefcount"):
libc=ctypes.CDLL("ucrtbase.dll")
else:
libc=ctypes.CDLL("msvcrt.dll")
kernel32=ctypes.WinDLL("kernel32")
c_stdout=kernel32.GetStdHandle(-11)
c_stderr=kernel32.GetStdHandle(-12)
classstore_stdout_stderr(object): # noqa: N801, UP004"""Store stdout and stderr in buffers. This class is used to store stdout and stderr in buffers. It is used to capture the output of the C code as well. It is used as a context manager. Examples: >>> with store_stdout_stderr() as (outbuff, errbuff): ... print("Hello World!") >>> outbuff.getvalue() 'Hello World!\\n' >>> errbuff.getvalue() '' >>> from io import StringIO >>> outbuff = StringIO() >>> errbuff = StringIO() >>> with store_stdout_stderr(outbuff, errbuff): ... print("Hello World!") >>> outbuff.getvalue() 'Hello World!\\n' >>> errbuff.getvalue() '' """_sys=sys_os=osdef__init__(
self,
outbuff: StringIO|None=None,
errbuff: StringIO|None=None,
disable: bool|None=None,
close_buff: bool|None=None) ->None:
"""Initialize the class. Args: outbuff (StringIO, optional): The buffer to store stdout in. If None, a new StringIO is created. Defaults to None. errbuff (StringIO, optional): The buffer to store stderr in. If None, a new StringIO is created. Defaults to None. disable (bool, optional): Disable the redirection. If None, disable is set to False. Defaults to None. close_buff (bool, optional): Close the buffers on context __exit__. If None, close_buff defaults to True if disable is None, False otherwise. Defaults to None. """self.outbuff=outbuffifoutbuffisnotNoneelseStringIO()
self.errbuff=errbuffiferrbuffisnotNoneelseStringIO()
self.disable=disableifdisableisnotNoneelseFalseself.close_buff=close_buffifclose_buffisnotNoneelsedisableisNonedef_flush_stdout_stderr(self) ->None:
"""Flush python and C stdout and stderr buffers."""self._sys.stdout.flush()
self._sys.stderr.flush()
ifnotWIN:
libc.fflush(c_stdout)
libc.fflush(c_stderr)
else:
libc.fflush(None)
def__enter__(self) ->tuple[StringIO, StringIO]:
ifself.disable:
returnself.outbuff, self.errbuff# If stdout/err do not have a fileno, we cannot redirect themifnothasattr(self._sys.stdout, "fileno") ornothasattr(self._sys.stderr, "fileno"):
self.disable=Truereturnself.outbuff, self.errbuff# Save the original file descriptorsself.old_stdout_fd=self._sys.stdout.fileno()
self.old_stderr_fd=self._sys.stderr.fileno()
# Duplicate the original file descriptorsself.old_stdout_dup=self._os.dup(self.old_stdout_fd)
self.old_stderr_dup=self._os.dup(self.old_stderr_fd)
# Create temporary files to store stdout and stderrself.stdout_tfile=tempfile.TemporaryFile(mode="w+b")
self.stderr_tfile=tempfile.TemporaryFile(mode="w+b")
# Save the original stdout and stderrself.old_stdout=self._sys.stdoutself.old_stderr=self._sys.stderrself._flush_stdout_stderr()
# Redirect stdout and stderr file descriptors to the temporary filesself._os.dup2(self.stdout_tfile.fileno(), self.old_stdout_fd)
self._os.dup2(self.stderr_tfile.fileno(), self.old_stderr_fd)
# Redirect stdout and stderr to the temporary filesself._sys.stdout=TextIOWrapper( # type: ignoreself._os.fdopen(
self.stdout_tfile.fileno(),
"wb",
closefd=False
),
encoding="utf-8",
write_through=True
)
self._sys.stderr=TextIOWrapper( # type: ignoreself._os.fdopen(
self.stderr_tfile.fileno(),
"wb",
closefd=False
),
encoding="utf-8",
write_through=True
)
returnself.outbuff, self.errbuffdef__exit__(self, *_) ->None:
ifself.disable:
return# Restore stdout and stderrself._flush_stdout_stderr()
# Redirect stdout and stderr file descriptors to the saved file descriptorsself._os.dup2(self.old_stdout_dup, self.old_stdout_fd)
self._os.dup2(self.old_stderr_dup, self.old_stderr_fd)
# return stdout and stderr to their original objects (TextIOWrapper)self._sys.stdout=self.old_stdout# type: ignoreself._sys.stderr=self.old_stderr# type: ignore# Flush the temporary filesself.stderr_tfile.flush()
self.stdout_tfile.flush()
# Seek to the beginning of the filesself.stderr_tfile.seek(0, SEEK_SET)
self.stdout_tfile.seek(0, SEEK_SET)
# Write the temporary files to the buffersself.outbuff.write(self.stdout_tfile.read().decode())
self.errbuff.write(self.stderr_tfile.read().decode())
# Close the saved file descriptorsself._os.close(self.old_stdout_dup)
self._os.close(self.old_stderr_dup)
# Close the temporary filesself.stdout_tfile.close()
self.stderr_tfile.close()
# Close the buffersifself.close_buff:
self.outbuff.close()
self.errbuff.close()
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Hello! Just wanted to share some code (WIP) I wrote to capture the output of e.g.
llama_print_timings
, mainly because I'm doing some testing on different models and thought it was useful to have:logging
module, but still have thellama.cpp
output availableAs such, I didn't want to turn off "verbose", so this code will work as a standalone drop-in context manager, compatible with
llama-cpp-python
if you create the model withverbose=True
. It could also be implemented somewhat trivially into the package code, but right now doesn't support/dev/null
(but does support dropping the output).Caveats
sys.__stderr__
which would indicate it uses the originalstderr
stream (therefore bypassing redirects).__exit__
), this is due to using a temporary file. I can think of a few ways to avoid this, maybe the easiest would be to create simple wrapper classes forTemporaryFile
andTextIOWrapper
so that onwrite*
methods it would simultaneously write to the buffer (also removing the need for subsequentflush()
,seek()
andread()
calls. It should work but may introduce issues with threads or if a C stream and a python stream are written to at the same time (would require testing).Tested on python 3.11 / Windows 11. When I have a moment, I'll test it in WSL/Linux and I know someone with a Mac I can ask later as well.
TODO
One final note, I'll probably refactor it a little so it is a bit more readable and efficient, specifically:
stdout
/stderr
streams on exception.Source Code
io.py
Beta Was this translation helpful? Give feedback.
All reactions