Repository: ollama/ollama (language: go)

Issue:
CLI display flickers in SSH session on pull
When pulling occasionally I see the loading bar flicker during and after download. This can be seen more dramatically on a fast connection.

System details:
```
OS: Debian 11
Terminal: Warp
Ollama: v0.1.17
```

https://github.com/jmorganca/ollama/assets/5853428/ec9c2410-f5c0-4a41-a9ef-73bee50b99f2


Discussion / hints:
This looks like a hardware acceleration issue with the terminal emulator. I could be way off here but I remember seeing stuff like this with Alacritty (another hardware accelerated terminal emulator) with other applications that use this style of loading bar. Also, unless I'm mistaken - it doesn't seem Warp is available for Linux yet (as per the site: https://www.warp.dev/) and doesn't seem to be open source. So im not sure how you're running this emulator (sorry if this is dumb and I'm missing something obvious here). Can you reproduce this in any other terminal emulators?
I see the same problem with iTerm2 on macOS.
I tested this again in Terminal and iTerm2 and I'm not seeing it in either. I checked iTerm2 and it does have hw acceleration turned on. I'm going to go ahead and close this.
Kindly reopen.

This issue was closed incorrectly, without proper investigation, just blindly blaming two different terminal emulators and their hardware acceleration.

The flicker occurs with at least 10 different terminal emulators, some of which don't even have hardware acceleration.

ollama explicitly sends out `\e[2K` escape sequences that do ask for the bottom two rows to be cleared momentarily, that is, to flicker.

Please see the detailed analysis at https://gitlab.gnome.org/GNOME/vte/-/issues/2837.
I can observe this on macOS with various terminals (iTerm, macOS' own terminal, kitty, ghostty) with zellij as terminal multiplexer.
I believe this issue is caused by the terminal rendering a frame in the middle of a UI update. In particular, rendering a frame _after_ the previous line is cleared:

https://github.com/ollama/ollama/blob/0667baddc658d3f556a369701819e7695477f59a/progress/progress.go#L87-L93

but _before_ the updated line is rendered:

https://github.com/ollama/ollama/blob/0667baddc658d3f556a369701819e7695477f59a/progress/progress.go#L95-L101

Some terminals support a [synchronized output](https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036) feature, where the program can send a pair of _begin_, _end_ escape codes to the terminal so that the terminal will not render any partial content it sees between _begin_ and _end_.

But there's also an even simpler solution that solves the problem on my machine: emit the entire state update as a single write. This requires writing the partial updates to a buffer, and then writing out the entire buffer in one go. That's what I implemented in #9079. As far as I know, this does not guarantee that the terminal won't render a partial frame. But I predict that it solves the problem in practice in almost all cases. I'd be interested to find out how it works for other folks who have observed this issue.
Synchronized output / atomic update, by far not supported by all terminals out there, can only be a _hint_. You can't enforce it, because if the closing sequence doesn't arrive within a reasonably short deadline, the terminal has to move on and print the already received data; it cannot afford to delay processing indefinitely and thus make the terminal entirely unusable for the user if the escape sequences don't actually arrive in pairs due to faulty / interrupted output.

Emitting the changes in a single write isn't a guarantee either. The writer may intent to emit a single chunk but may result in a short write which is later continued. The terminal line discipline, or any intermediate layer in between (e.g. ssh, tmux) can split the packet into smaller ones. Any terminal emulator might read smaller chunks and decide to update its UI after one of them.

These techniques could more or less lower the risk of flicker, but could not fully eliminate.

Why go for a fragile solution rather than a robust one, which is also probably the simplest? Just don't clear the display. Simply overwrite the old contents with the desired new one. (Clear to the end of the line _after_ printing the new contents of the line, if you're not yet at the right margin and there's a chance that the old contents were wider than the new one.)


Fix patch:
diff --git a/progress/progress.go b/progress/progress.go
index 102830a82cb..0cd0ea1f97b 100644
--- a/progress/progress.go
+++ b/progress/progress.go
@@ -1,6 +1,7 @@
 package progress
 
 import (
+	"bufio"
 	"fmt"
 	"io"
 	"sync"
@@ -13,7 +14,8 @@ type State interface {
 
 type Progress struct {
 	mu sync.Mutex
-	w  io.Writer
+	// buffer output to minimize flickering on all terminals
+	w *bufio.Writer
 
 	pos int
 
@@ -22,7 +24,7 @@ type Progress struct {
 }
 
 func NewProgress(w io.Writer) *Progress {
-	p := &Progress{w: w}
+	p := &Progress{w: bufio.NewWriter(w)}
 	go p.start()
 	return p
 }
@@ -48,11 +50,14 @@ func (p *Progress) Stop() bool {
 	stopped := p.stop()
 	if stopped {
 		fmt.Fprint(p.w, "\n")
+		p.w.Flush()
 	}
 	return stopped
 }
 
 func (p *Progress) StopAndClear() bool {
+	defer p.w.Flush()
+
 	fmt.Fprint(p.w, "\033[?25l")
 	defer fmt.Fprint(p.w, "\033[?25h")
 
@@ -81,20 +86,24 @@ func (p *Progress) render() {
 	p.mu.Lock()
 	defer p.mu.Unlock()
 
+	defer p.w.Flush()
+
+	// eliminate flickering on terminals that support synchronized output
+	fmt.Fprint(p.w, "\033[?2026h")
+	defer fmt.Fprint(p.w, "\033[?2026l")
+
 	fmt.Fprint(p.w, "\033[?25l")
 	defer fmt.Fprint(p.w, "\033[?25h")
 
-	// clear already rendered progress lines
-	for i := range p.pos {
-		if i > 0 {
-			fmt.Fprint(p.w, "\033[A")
-		}
-		fmt.Fprint(p.w, "\033[2K\033[1G")
+	// move the cursor back to the beginning
+	for range p.pos - 1 {
+		fmt.Fprint(p.w, "\033[A")
 	}
+	fmt.Fprint(p.w, "\033[1G")
 
 	// render progress lines
 	for i, state := range p.states {
-		fmt.Fprint(p.w, state.String())
+		fmt.Fprint(p.w, state.String(), "\033[K")
 		if i < len(p.states)-1 {
 			fmt.Fprint(p.w, "\n")
 		}


Test patch: