108 lines
2.2 KiB
Go
108 lines
2.2 KiB
Go
package llm
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// StatusWriter is a writer that captures error messages from the llama runner process
|
|
type StatusWriter struct {
|
|
out io.Writer
|
|
// StartRunner wires both Stdout and Stderr to the same StatusWriter, and
|
|
// os/exec serializes Write calls in that case.
|
|
lastErrMsg atomic.Value
|
|
}
|
|
|
|
const maxCapturedErrorBytes = 8 * 1024
|
|
|
|
func NewStatusWriter(out io.Writer) *StatusWriter {
|
|
return &StatusWriter{
|
|
out: out,
|
|
}
|
|
}
|
|
|
|
func (w *StatusWriter) LastError() string {
|
|
if w == nil {
|
|
return ""
|
|
}
|
|
if v := w.lastErrMsg.Load(); v != nil {
|
|
return v.(string)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (w *StatusWriter) SetLastError(msg string) {
|
|
if w == nil {
|
|
return
|
|
}
|
|
w.lastErrMsg.Store(msg)
|
|
}
|
|
|
|
func (w *StatusWriter) AppendError(msg string) {
|
|
if w == nil || msg == "" {
|
|
return
|
|
}
|
|
|
|
if current := w.LastError(); current != "" {
|
|
msg = current + "\n" + msg
|
|
}
|
|
|
|
if len(msg) > maxCapturedErrorBytes {
|
|
msg = msg[len(msg)-maxCapturedErrorBytes:]
|
|
if i := strings.IndexByte(msg, '\n'); i >= 0 {
|
|
msg = msg[i+1:]
|
|
}
|
|
}
|
|
|
|
w.SetLastError(msg)
|
|
}
|
|
|
|
// TODO - regex matching to detect errors like
|
|
// libcublasLt.so.11: cannot open shared object file: No such file or directory
|
|
// TODO - if we later see error lines split across multiple Write calls in real
|
|
// logs, add a small rolling buffer here to capture those fragments.
|
|
|
|
var errorPrefixes = []string{
|
|
"mlx:",
|
|
"MLX:",
|
|
"panic:",
|
|
"fatal error:",
|
|
"error:",
|
|
"Error:",
|
|
"CUDA error",
|
|
"ROCm error",
|
|
"cudaMalloc failed",
|
|
"\"ERR\"",
|
|
"error loading model",
|
|
"GGML_ASSERT",
|
|
"Deepseek2 does not support K-shift",
|
|
"signal arrived during cgo execution",
|
|
"llama_init_from_model:",
|
|
}
|
|
|
|
func (w *StatusWriter) Write(b []byte) (int, error) {
|
|
var errMsg string
|
|
errStart := -1
|
|
var errPrefix string
|
|
for _, prefix := range errorPrefixes {
|
|
if i := bytes.Index(b, []byte(prefix)); i >= 0 && (errStart < 0 || i < errStart) {
|
|
errStart = i
|
|
errPrefix = prefix
|
|
}
|
|
}
|
|
if errStart >= 0 {
|
|
line := b[errStart+len(errPrefix):]
|
|
if j := bytes.IndexByte(line, '\n'); j >= 0 {
|
|
line = line[:j]
|
|
}
|
|
errMsg = errPrefix + string(bytes.TrimRight(line, " \t\r"))
|
|
}
|
|
if errMsg != "" {
|
|
w.AppendError(errMsg)
|
|
}
|
|
|
|
return w.out.Write(b)
|
|
}
|