Your SSH-Launched Benchmark Is Running at nice 5
On macOS, backgrounding a process from a non-interactive SSH shell silently deprioritizes it. Every number you compare against a system daemon is invalid.
Summary
Any process backgrounded with nohup โฆ & from a non-interactive SSH shell on macOS inherits Darwin background quality-of-service and runs at nice 5. A launchd daemon runs at nice 0. So a server you start over SSH to benchmark is deprioritized against the exact production daemon you're comparing it to, silently, and every number is invalid. The fix is one word: screen.
The measurement
Verified side by side on the same machine:
| launch method | ps -Ao pid,pri,ni |
|---|---|
nohup sh -c "sleep 40" & over SSH |
pri 31, ni 5 |
screen -dmS t sh -c "sleep 40" over SSH |
pri 31, ni 0 |
| launchd system daemon | pri 4, ni 0 |
This is macOS-specific. Linux hosts don't do it โ our GPU servers are unaffected. It applies to every Mac you drive over SSH, which for us is all of them.
What it cost us
A hand-launched inference server produced 28.5 tok/s prefill on a 2,560-token prompt โ 89.8 seconds โ and a 25k-token prompt that hadn't finished after ten minutes. On a completely idle machine. Against a production median of about 320 tok/s.
The model was fine. The checkpoint was fine. The prompt was fine. The server was at nice 5.
We spent the session looking for a software regression that didn't exist. It's a particularly nasty failure because everything about the setup looks careful: idle box, controlled prompt, same binary, same flags. The one uncontrolled variable is invisible unless you go looking for it, and nothing in the output hints at it.
It's also a failure that biases in a consistent direction. Your hand-launched candidate always loses to the incumbent daemon. If you're evaluating a replacement for a production service โ which is exactly when you'd be hand-launching something โ the bias points at "keep what we have."
The fix
screen is preinstalled at /usr/bin/screen on macOS. tmux generally isn't.
screen -wipe >/dev/null 2>&1
screen -dmS <name> sh -c "ENV=x /path/to/server --flags > /tmp/out.log 2>&1"
# verify BEFORE trusting any number:
ps -Ao pid,pri,ni,command | grep "[m]yserver" # want ni=0
The verification line is the important part. Add it to the harness, not to your memory.
Things that don't work
We tried these before landing on screen:
taskpolicy -B -p <pid>does not clear it. Measured; nice stayed at 5.taskpolicy -B <program>isn't valid syntax โ-Brequires-p pid.reniceto a lower value requires root, so you can't repair a running process after the fact without sudo. On a headless box where sudo needs a TTY, that means you can't repair it at all.
Since you can't fix it retroactively, the check has to happen before the run, not after you notice a weird number.
The general rule
Before benchmarking any hand-started service on a Mac, print ps -o pri,ni for it and confirm ni=0. If it says 5, the run is garbage.
The broader version: when you compare a hand-launched process against a system-managed one, you are comparing two different execution environments, not two different programs. Priority is the one that bit us. Environment variables, working directory, open file limits, and the sandbox profile are all in the same category โ the service manager sets them and your shell doesn't.
Key takeaways
nohup โฆ &over non-interactive SSH on macOS yieldsnice 5. A launchd daemon isnice 0. Your benchmark is throttled against its own comparison.- Use
screen -dmSโ preinstalled on macOS, and it producesnice 0. - Verify with
ps -o pri,nibefore trusting a number, because you can't repair the priority afterward without root. - The bias has a direction. Hand-launched candidates always lose to installed daemons, which is the opposite of what you want when evaluating a replacement.
- A hand-started service differs from a managed one in more than priority. Priority is just the one that's invisible.
Everything in these notes I also do for hire: local AI set up on hardware you own, configured on-site, then handed over with enough documentation that you do not need me afterward. If that sounds more useful than another weekend of reading forum threads, the details are at /hire. No obligation from an email, and the posts stay free either way.