<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Bizarre adventures in computer screen</title>
<description>Bizarre adventures in computer screen</description>
<generator>Emacs webfeeder.el</generator>
<link>https://goritskov.com/</link>
<atom:link href="https://goritskov.com/guix.xml" rel="self" type="application/rss+xml"/>
<lastBuildDate>Wed, 07 Oct 2026 08:16:23 +0000</lastBuildDate>
<item>
  <title>[Run Your Own] Running llama-swap as a proxy for llama.cpp on Guix</title>
  <description><![CDATA[<div id="content" class="content">

 <p>
So, we are not finished with locally running LLMs yet.
</p>

 <p>
After some time spent using  <code>llama-cpp-server</code> from the  <a href="run_your_own_part_1_llama_server.html">previous blog post</a>, I faced a couple of nice-to-haves:
</p>
 <ul class="org-ul"> <li>Ability to run different models by specifying desired one during the request</li>
 <li>Scale down models when they are not used to save resources</li>
</ul> <p>
So, I decided to refactor earlier created  <code>llama-cpp-server</code> to be able to run multiple models (not in parallel, but by choice).
</p>
 <div id="outline-container-orge861568" class="outline-2">
 <h2 id="orge861568">Idea</h2>
 <div class="outline-text-2" id="text-orge861568">
 <p>
I started by trying to find any tool available for these nice-to-haves - and found a project called  <a href="https://github.com/mostlygeek/llama-swap">llama-swap</a>.
It is licensed under the MIT license and allows all the things I wanted to:
</p>

 <div class="org-src-container">
 <pre class="src src-nil"> <code>  Run multiple generative AI models on your machine and hot-swap between them on demand. llama-swap works with any OpenAI and Anthropic API compatible server and is used by thousands of people to power their local AI workflows.

Built in Go for performance and simplicity, llama-swap has zero dependencies and is incredibly easy to set up. Get started in minutes - just one binary and one configuration file.
</code></pre>
</div>

 <p>
So, it is written in Go (so I can package it in Guix relatively easily) and can swap between models.
I already had a service config for running  <code>llama-cpp-server</code>, so why not modify it just a little bit (hopefully!) for llama-swap?
</p>
</div>
</div>
 <div id="outline-container-orgb83e5a6" class="outline-2">
 <h2 id="orgb83e5a6">A little bit</h2>
 <div class="outline-text-2" id="text-orgb83e5a6">
 <p>
I decided to reuse previously written config as much as possible - so  <code>llama-cpp-configuration</code> was simplified and now used inside a new llama-swap-configuration:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(define-configuration/no-serialization llama-swap-configuration
  (host
    (string &quot;127.0.0.1&quot;)
    &quot;Host to run server on.&quot;)
  (port
   (string &quot;8080&quot;)
   &quot;Port to run server on.&quot;)
  (llamas
   (list '())
   &quot;List of llama-cpp-configuration to run&quot;)
  (with-nvidia?
   (boolean #f)
   &quot;Enable hack for running on Nvidia with Vulkan&quot;)
  (nvidia-driver-package
   (package nvidia-driver)
   &quot;Nvidia driver package to use&quot;)
  (nvidia-modprobe-package
   (package nvidia-modprobe)
   &quot;Nvidia modprobe package to use&quot;)
  (user
   (string &quot;llama&quot;)
   &quot;User to run as.&quot;)
  (group
   (string &quot;llama&quot;)
   &quot;Group to run as.&quot;)
  (timeout
   (string &quot;2400&quot;)
   &quot;Model startup timeout in seconds.&quot;)
  (log-to-stdout
   (string &quot;both&quot;)
   &quot;Logging: proxy, upstream, both or none. Defaults to both&quot;)
  (requirements
   (list '())
   &quot;List of additional service requirements.&quot;))

(define-configuration/no-serialization llama-cpp-configuration
  (name
   (string)
   &quot;Model name to be used in API&quot;)
  (huggingface-name
   (string)
   &quot;Huggingface model to run.&quot;)
  (parameters
   (list '())
   &quot;Model parameters.&quot;))
</code></pre>
</div>

 <p>
Most of the settings (except nvidia-related ones) are for the  <code>llama-swap</code> itself. Nvidia settings actually apply to all specified  <code>llama-cpp-configurations</code> - I thought it would be enough configurability for now (:
</p>

 <p>
Of course, I had to modify the Shepherd service a little bit - it runs a different binary now and I also need to make sure the  <code>llama-server</code> binary is in the PATH, so llama-swap could run it when needed.
</p>

 <p>
 <a href="https://git.goritskov.com/little-guix-channel/tree/rodion/services/llama.scm?id=4eefdec33cf2a052a28b534d37a85f6ed9056e4a">Here is</a> the result.
</p>

 <p>
 <code>llama-cpp-server</code> is gone, now  <code>llama-swap-server</code> could be used in place. Even if you have only one model to run, it could be useful
with lazy loading of models and scaling down models when they are unused.
</p>

 <p>
There are a lot of changes, so I will try to comment on the caveats I faced.
</p>
</div>
 <div id="outline-container-orge1b7cd2" class="outline-3">
 <h3 id="orge1b7cd2">Caveats</h3>
 <div class="outline-text-3" id="text-orge1b7cd2">
</div>
 <div id="outline-container-orgf3e332a" class="outline-4">
 <h4 id="orgf3e332a">Packaging</h4>
 <div class="outline-text-4" id="text-orgf3e332a">
 <p>
 <code>llama-swap</code> was not packaged for Guix, but with the Go importer it was  <a href="https://git.goritskov.com/little-guix-channel/tree/rodion/packages/llm.scm?id=4eefdec33cf2a052a28b534d37a85f6ed9056e4a#n37">relatively easy to package</a> it for my personal channel:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(define-public llama-swap
  (package
    (name &quot;llama-swap&quot;)
    (version &quot;228&quot;)
    (source
     (origin
       (method git-fetch)
       (uri (git-reference
              (url &quot;https://github.com/mostlygeek/llama-swap&quot;)
              (commit (string-append &quot;v&quot; version))))
       (file-name (git-file-name name version))
       (sha256
        (base32 &quot;1yxml6n4lyc8galm0q4jfxhlnhjkl2sbvsy2qzddc71rm90d7khh&quot;))))
    (build-system go-build-system)
    (arguments
     (list
      #:tests? #f ;; TODO: Skip failing tests
      #:import-path &quot;github.com/mostlygeek/llama-swap&quot;
      #:phases
      #~(modify-phases %standard-phases
          ;; Force disable legacy ServeMux behaviour, with which routing
          ;; doesn't work.  It is enabled automatically because we use
          ;; GO111MODULE=off in go-build-system.
          (add-after 'unpack 'fix-httpmuxgo121
            (lambda _
              (substitute* &quot;src/github.com/mostlygeek/llama-swap/llama-swap.go&quot;
                ((&quot;^package main&quot;)
                 &quot;//go:debug httpmuxgo121=0\n\npackage main&quot;)))))))
    (inputs (list go-github-com-stretchr-testify
                  go-github-com-shirou-gopsutil-v4
                  go-github-com-billziss-gh-golib
                  go-github-com-tidwall-gjson
                  go-github-com-tidwall-sjson
                  go-github-com-klauspost-compress
                  go-github-com-fxamacker-cbor-v2
                  go-gopkg-in-yaml-v3

                  go-github-com-gin-gonic-gin
                  go-github-com-charmbracelet-bubbles
                  go-github-com-charmbracelet-lipgloss
                  go-github-com-google-jsonschema-go))
    (home-page &quot;https://github.com/mostlygeek/llama-swap&quot;)
    (synopsis &quot;llama-swap&quot;)
    (description
     &quot;llama-swap is an @code{OpenAI} API compatible server that gives you complete
control over how you use your hardware.  It automatically swaps to the
configuration of your choice for serving a model.  Since
@@url{https://github.com/ggerganov/llama.cpp/tree/master/examples/server,llama.cpp's
server} can't swap models, let's swap the server instead!&quot;)
    (license license:expat)))
</code></pre>
</div>

 <p>
There are a couple interesting things to mention about this package.
</p>

 <p>
The first one is that I had to force disable legacy ServeMux behaviour. Because of the way Guix build Go applications (with Go modules disabled), some apps end up using the wrong ServeMux implementation and get 404s on correct paths. To overcome this issue a go:debug entry was added to the main file.
</p>

 <p>
And the other one was UI - actually llama-swap has some fancy frontend, but as it is nearly impossible to build frontend in Guix and I don't use it (I am mostly working with LLM from Emacs), I decided just not to build it. It doesn't break anything from the API point of view, so it is fine.
</p>
</div>
</div>
 <div id="outline-container-org22dff76" class="outline-4">
 <h4 id="org22dff76">Creation of YAML configuration file for llama-swap</h4>
 <div class="outline-text-4" id="text-org22dff76">
 <p>
I created the YAML file by using strings with spaces and newlines, not a lot of magic here:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(define (serialize-llama-cpp-configuration config)
  (match-record config &lt;llama-cpp-configuration&gt;
                (name huggingface-name parameters)
    (let ((llama-server-args (append (list &quot;--port ${PORT}&quot;
                                           (string-append &quot;-hf &quot; huggingface-name))
                                     parameters)))
      (format #f &quot;  ~a:\n   cmd: llama-server ~a\n&quot; name (string-join llama-server-args &quot; &quot;)))))

(define (llama-swap-config-file config)
  (match-record config &lt;llama-swap-configuration&gt;
                (llamas timeout log-to-stdout)
    (mixed-text-file &quot;llama-swap-conf.yaml&quot; &quot;
healthCheckTimeout: &quot; timeout &quot;
logToStdout: &quot; log-to-stdout &quot;
models:
&quot; (string-join (map serialize-llama-cpp-configuration llamas) &quot;&quot;))))
</code></pre>
</div>

 <p>
In the end I have a YAML configuration file with  <code>llama-server</code> command lines:
</p>

 <div class="org-src-container">
 <pre class="src src-yaml"> <code>rodion@bumblebee-mighty ~$ cat /gnu/store/m3hpzgjyl66m01j1q8la2ddi0514q0l0-llama-swap-conf.yaml

healthCheckTimeout: 2400
logToStdout: both
models:
  qwen-3.5:
   cmd: llama-server --port ${PORT} -hf unsloth/Qwen3.5-27B-GGUF:UD-Q4_K_XL -c 64000 --temp 0.6 --top-k 20 --top-p 0.95 --min-p 0.00
  qwen-3.6:
   cmd: llama-server --port ${PORT} -hf unsloth/Qwen3.6-27B-MTP-GGUF:UD-Q4_K_XL -c 64000 --temp 0.6 --top-k 20 --top-p 0.95 --min-p 0.00
</code></pre>
</div>

 <p>
Not a big problem, but it required some time to make it more or less readable.
</p>
</div>
</div>
 <div id="outline-container-orge2b09aa" class="outline-4">
 <h4 id="orge2b09aa">Make it run with Vulkan on Mesa</h4>
 <div class="outline-text-4" id="text-orge2b09aa">
 <p>
The previous version of llama-cpp-service failed to run on Mesa, so I had to also fix it.
</p>
</div>
 <div id="outline-container-orge0a2c12" class="outline-5">
 <h5 id="orge0a2c12">Devices</h5>
 <div class="outline-text-5" id="text-orge0a2c12">
 <p>
As I run  <code>llama-swap</code> using  <code>least-authority-wrapper</code>, I have to create a filesystem mapping for all the necessary devices. And it turned out I had missed a couple of them before:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(device-mappings
 (append
  (list (file-system-mapping
          (source &quot;/dev/dri&quot;)
          (target source)
          (writable? #t))
        (file-system-mapping
          (source &quot;/sys/dev&quot;)
          (target source)
          (writable? #t))
        (file-system-mapping
          (source &quot;/sys/devices&quot;)
          (target source)
          (writable? #t)))
  (if with-nvidia?
      (map (lambda (dev)
             (file-system-mapping
               (source dev)
               (target source)
               (writable? #t)))
           '(&quot;/dev/nvidiactl&quot;
             &quot;/dev/nvidia0&quot;
             &quot;/dev/nvidia-modeset&quot;
             &quot;/dev/nvidia-uvm&quot;
             &quot;/dev/nvidia-uvm-tools&quot;))
      '())))
</code></pre>
</div>

 <p>
The important part here is the addition of  <code>/sys/dev</code> and  <code>/sys/devices</code>, as they appeared to be necessary for Vulkan to work in Mesa. Yes, probably these mappings are too broad (I could try mounting only specific devices), but I am OK with this for now.
</p>
</div>
</div>
 <div id="outline-container-orgd85703b" class="outline-5">
 <h5 id="orgd85703b">Vulkan drivers</h5>
 <div class="outline-text-5" id="text-orgd85703b">
 <p>
The next problem was Vulkan ICDs for Mesa. I thought that setting the environment variable  <code>VK_ICD_FILENAMES</code> to Mesa's icd.d directory would be enough, but it actually wasn't, because  <code>VK_ICD_FILENAMES</code>  <a href="https://github.com/KhronosGroup/Vulkan-Loader/blob/v1.4.335/docs/LoaderDriverInterface.md#overriding-the-default-driver-discovery">doesn't support directories</a>, only files. I tried  <code>VK_DRIVER_FILES</code>, but it didn't work with llama.cpp. So I decided to go hacky and construct the list of all Mesa's Vulkan ICDs myself - now  <code>make-forkexec-constructor</code> call looks like this:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(make-forkexec-constructor
 (append (list #$llama-swap
               &quot;-listen&quot; (string-append #$host &quot;:&quot; #$port)
               &quot;-config&quot; #$config-file))
 #:environment-variables
 (list (string-append &quot;HOME=&quot; #$%llama-home)
       &quot;SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt&quot;
       (string-append &quot;VK_ICD_FILENAMES=&quot; (if #$with-nvidia?
                                              #$icd
                                              (string-join (map (lambda (file) (string-append #$icd &quot;/&quot; file))
                                                                (scandir #$icd
                                                                         (lambda (dir) (string-suffix? &quot;.json&quot; dir))))
                                                           &quot;:&quot;)))
       (string-append &quot;PATH=&quot; #$llama-cpp &quot;/bin&quot;)))
</code></pre>
</div>

 <p>
 <code>icd</code> here is created like this - it is a file for Nvidia case and directory for Mesa:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(if with-nvidia?
    (mixed-text-file &quot;nvidia_icd.x86_64.json&quot; &quot;{
    \&quot;file_format_version\&quot; : \&quot;1.0.1\&quot;,
    \&quot;ICD\&quot;: {
        \&quot;library_path\&quot;: \&quot;&quot; nvidia-driver-package &quot;/lib/libEGL_nvidia.so.0\&quot;,
        \&quot;api_version\&quot; : \&quot;1.4.312\&quot;
    }
}&quot;)
    (file-append mesa &quot;/share/vulkan/icd.d&quot;))
</code></pre>
</div>
</div>
</div>
</div>
 <div id="outline-container-org7bb7895" class="outline-4">
 <h4 id="org7bb7895">Making service work with Nvidia on headless device</h4>
 <div class="outline-text-4" id="text-org7bb7895">
 <p>
While working on  <code>llama-swap-service</code>, I finally removed all desktop-related software from the machine configuration (it was running KDE before) and moved it to a separate room. So it became headless. And this moment I found out that llama-swap-service stopped working after reboot.
</p>

 <p>
The problem was that for the Nvidia device to work in headless mode, I had to load the kernel module and initialize all necessary devices before I actually ran anything.
</p>

 <p>
So I added some more commands to the Shepherd service startup when the service was configured to be run on Nvidia:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(lambda args
  (when #$with-nvidia?
    (format #t &quot;llama-cpp: running nvidia-modprobe~%&quot;)
    (system* #$(file-append nvidia-modprobe-package &quot;/bin/nvidia-modprobe&quot;) &quot;-c0&quot; &quot;-m&quot;)
    (format #t &quot;llama-cpp: running nvidia-smi~%&quot;)
    (system* #$(file-append nvidia-driver-package &quot;/bin/nvidia-smi&quot;))
    (format #t &quot;llama-cpp: waiting for nvidia devices~%&quot;)
    (let loop ((remaining 300))
      (cond
       ((every file-exists? nvidia-devices)
        (format #t &quot;llama-cpp: nvidia devices ready~%&quot;))
       ((zero? remaining)
        (error &quot;llama-cpp: timed out waiting for nvidia devices&quot;))
       (else
        (sleep 1)
        (loop (- remaining 1))))))
  (apply forkexec args))
</code></pre>
</div>

 <p>
Before starting the actual  <code>llama-swap</code> under  <code>least-authority-wrapper</code> I now make sure all necessary setup commands are run and all necessary devices are present.
</p>
</div>
</div>
</div>
</div>
 <div id="outline-container-orgcfb0201" class="outline-2">
 <h2 id="orgcfb0201">Example configuration</h2>
 <div class="outline-text-2" id="text-orgcfb0201">
 <p>
Currently, I have two configurations which I use daily.
</p>

 <p>
Here is the one for a beefy Nvidia-powered machine:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(use-modules (nongnu packages nvidia)
             (rodion services llama))

(service llama-swap-service-type
         (llama-swap-configuration
          (host &quot;0.0.0.0&quot;)
          (with-nvidia? #t)
          (nvidia-driver-package nvidia-driver-new-feature)
          (nvidia-modprobe-package nvidia-modprobe-new-feature)
          (llamas (list (llama-cpp-configuration
                         (name &quot;qwen-3.5&quot;)
                         (huggingface-name &quot;unsloth/Qwen3.5-27B-GGUF:UD-Q4_K_XL&quot;)
                         (parameters (list &quot;-c&quot; &quot;64000&quot;
                                           &quot;--temp&quot; &quot;0.6&quot;
                                           &quot;--top-k&quot; &quot;20&quot;
                                           &quot;--top-p&quot; &quot;0.95&quot;
                                           &quot;--min-p&quot; &quot;0.00&quot;)))
                        (llama-cpp-configuration
                         (name &quot;qwen-3.6&quot;)
                         (huggingface-name &quot;unsloth/Qwen3.6-27B-MTP-GGUF:UD-Q4_K_XL&quot;)
                         (parameters (list &quot;-c&quot; &quot;64000&quot;
                                           &quot;--temp&quot; &quot;0.6&quot;
                                           &quot;--top-k&quot; &quot;20&quot;
                                           &quot;--top-p&quot; &quot;0.95&quot;
                                           &quot;--min-p&quot; &quot;0.00&quot;)))))))
</code></pre>
</div>

 <p>
And here is the configuration for the laptop:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(use-modules (rodion services llama))

(service llama-swap-service-type
         (llama-swap-configuration
          (host &quot;127.0.0.1&quot;)
          (with-nvidia? #f)
          (llamas (list (llama-cpp-configuration
                         (name &quot;apertus-8b&quot;)
                         (huggingface-name &quot;unsloth/Apertus-8B-Instruct-2509-GGUF:UD-Q4_K_XL&quot;)
                         (parameters (list &quot;-c&quot; &quot;9600&quot;)))
                        (llama-cpp-configuration
                         (name &quot;qwen-7b&quot;)
                         (huggingface-name &quot;Qwen/Qwen2.5-7B-Instruct-GGUF&quot;)
                         (parameters '(&quot;-c&quot; &quot;9600&quot;)))))))
</code></pre>
</div>

 <p>
No additional problems found with this setup, so will keep using it for some time, hopefully (:
Have no plans of any breaking changes for this, so feel free to use it if you want.
</p>

 <p>
It is available in  <a href="https://toys.whereis.social/channels?search=little-guix-channel">my channel</a>.
</p>

 <p>
Happy hacking!
</p>
</div>
</div>
</div>]]></description>
  <category>guix</category>
  <category>llm</category>  <link>https://goritskov.com/posts/run_your_own_part_2_llama_swap.html</link>
  <guid isPermaLink="false">https://goritskov.com/posts/run_your_own_part_2_llama_swap.html</guid>
  <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
</item>
<item>
  <title>[Run Your Own] Running llama.cpp server on Guix</title>
  <description><![CDATA[<div id="content" class="content">

 <p>
This is a start to a (hopefully) series of posts about running different kinds of things
on Guix distro.
</p>
 <div id="outline-container-orgaff4ae4" class="outline-2">
 <h2 id="orgaff4ae4">Idea</h2>
 <div class="outline-text-2" id="text-orgaff4ae4">
 <p>
Even with all the ethical problems with LLMs (and, especially, companies providing them),
technology could be useful enough for at least giving it a try.
</p>

 <p>
Luckily, a couple of years ago I thought that I wanted to play some AAA games. I had a decent
sum of money in hand, so I decided to buy a beefy configuration with RTX 4090 GPU.
I haven't actually played anything seriously, except some hopeless crashes in Microsoft Flight Simulator to overcome my fear of flying, so most of the time the computer was gathering dust.
</p>

 <p>
Until one not-so-sunny Berlin Saturday morning, when I finally decided to load it with
local LLM inference.
</p>
</div>
</div>
 <div id="outline-container-orgc468f5c" class="outline-2">
 <h2 id="orgc468f5c">How to run?</h2>
 <div class="outline-text-2" id="text-orgc468f5c">
 <p>
Question was answered pretty fast - Guix has llama.cpp packaged and it has server mode, which suits nicely. However, two problems appeared:
</p>

 <ol class="org-ol"> <li>To use RTX 4090 fully I need to run proprietary drivers</li>
 <li>There is no Guix system service for running llama.cpp as a server</li>
</ol></div>
 <div id="outline-container-org44ed223" class="outline-3">
 <h3 id="org44ed223">On drivers</h3>
 <div class="outline-text-3" id="text-org44ed223">
 <p>
Thanks to Nonguix channel, running proprietary Nvidia drivers is not a big problem.
You just have to carefully follow instructions  <a href="https://gitlab.com/nonguix/nonguix/-/blob/master/README.org">from the README</a>. Nowadays I am running nvda-595 driver and it is working fine (as fine as NVIDIA could on Linux).
</p>
</div>
</div>
 <div id="outline-container-org6274087" class="outline-3">
 <h3 id="org6274087">On llama.cpp system service</h3>
 <div class="outline-text-3" id="text-org6274087">
 <p>
This thing is a little bit more complicated. We have to provide correct Vulkan environment
variables to make use of Nvidia proprietary drivers in Guix. To simplify these things
I decided to pack all the complexity inside the Guix system service.
</p>

 <p>
As this service uses proprietary software, there is no way it could be merged in the main Guix. But nobody can stop me from implementing it  <a href="https://toys.whereis.social/channels?search=little-guix-channel">in my own channel</a>.
</p>

 <p>
So, here is  <a href="https://git.goritskov.com/little-guix-channel/tree/rodion/services/llama.scm?id=fc47657de92b88ee54b4e7bb53121f3f502c574b">llama-cpp-service-type</a>. For now it only supports running GGUF models from HuggingFace via Vulkan backend. Following configuration parameters are available:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(define-configuration/no-serialization llama-cpp-configuration
  (host
    (string &quot;127.0.0.1&quot;)
    &quot;Host to run server on.&quot;)
  (port
   (string &quot;8080&quot;)
   &quot;Port to run server on.&quot;)
  (huggingface-name
   (string)
   &quot;Huggingface model to run.&quot;)
  (parameters
   (list '())
   &quot;Model parameters.&quot;)
  (with-nvidia?
   (boolean #f)
   &quot;Enable hack for running on Nvidia with Vulkan&quot;)
  (nvidia-driver-package
   (package nvidia-driver)
   &quot;Nvidia driver package to use&quot;)
  (user
   (string &quot;llama&quot;)
   &quot;User to run as.&quot;)
  (group
   (string &quot;llama&quot;)
   &quot;Group to run as.&quot;)
  (requirements
   (list '())
   &quot;List of additional service requirements.&quot;))
</code></pre>
</div>

 <p>
So, the only caveat here is providing correct Vulkan ICD path. Let's check it in detail:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(define (llama-cpp-shepherd-service config)
  (match-record config &lt;llama-cpp-configuration&gt;
                (huggingface-name parameters with-nvidia?
                                  nvidia-driver-package user
                                  group requirements
                                  host port)
    (let* ((icd (if with-nvidia?
                    (mixed-text-file &quot;nvidia_icd.x86_64.json&quot; &quot;{
    \&quot;file_format_version\&quot; : \&quot;1.0.1\&quot;,
    \&quot;ICD\&quot;: {
        \&quot;library_path\&quot;: \&quot;&quot; nvidia-driver-package &quot;/lib/libEGL_nvidia.so.0\&quot;,
        \&quot;api_version\&quot; : \&quot;1.4.312\&quot;
    }
}&quot;)
                    (file-append mesa &quot;/share/vulkan/icd.d&quot;)))
           (device-mappings
            (append
             (list (file-system-mapping
                     (source &quot;/dev/dri&quot;)
                     (target source)
                     (writable? #t)))
             (if with-nvidia?
                 (map (lambda (dev)
                        (file-system-mapping
                          (source dev)
                          (target source)
                          (writable? #t)))
                      '(&quot;/dev/nvidiactl&quot;
                        &quot;/dev/nvidia0&quot;
                        &quot;/dev/nvidia-modeset&quot;
                        &quot;/dev/nvidia-uvm&quot;
                        &quot;/dev/nvidia-uvm-tools&quot;))
                 '())))
           (llama-server
            (least-authority-wrapper
             (file-append llama-cpp &quot;/bin/llama-server&quot;)
             #:name &quot;llama-server&quot;
             #:user user
             #:group group
             #:preserved-environment-variables
             (append %default-preserved-environment-variables
                     '(&quot;SSL_CERT_FILE&quot; &quot;VK_ICD_FILENAMES&quot; &quot;HOME&quot;))
             #:mappings
             (append
              (list (file-system-mapping
                      (source &quot;/etc/ssl/certs/ca-certificates.crt&quot;)
                      (target source)
                      (writable? #f))
                    (file-system-mapping
                      (source %llama-home)
                      (target source)
                      (writable? #t))
                    (file-system-mapping
                      (source (if with-nvidia? nvidia-driver-package mesa))
                      (target source)
                      (writable? #f))
                    (file-system-mapping
                      (source icd)
                      (target source)
                      (writable? #f)))
              device-mappings)
             #:namespaces
             (fold delq spaces '(net user)))))
      (list (shepherd-service
              (documentation &quot;Llama-cpp server&quot;)
              (provision '(llama-cpp-server))
              (requirement (append '(networking) requirements))
              (start #~(make-forkexec-constructor
                        (append (list #$llama-server
                                      &quot;--host&quot; #$host
                                      &quot;--port&quot; #$port
                                      &quot;-hf&quot; #$huggingface-name
                                      #$@parameters))
                        #:environment-variables
                        (list (string-append &quot;HOME=&quot; #$%llama-home)
                              &quot;SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt&quot;
                              (string-append &quot;VK_ICD_FILENAMES=&quot; #$icd))))
              (stop #~(make-kill-destructor)))))))
</code></pre>
</div>

 <p>
There are basically three parts:
</p>

 <ol class="org-ol"> <li> <p>
Determining Vulkan ICD path depending on passed with-nvidia? flag and Nvidia driver version.
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(if with-nvidia?
    (mixed-text-file &quot;nvidia_icd.x86_64.json&quot; &quot;{
    \&quot;file_format_version\&quot; : \&quot;1.0.1\&quot;,
    \&quot;ICD\&quot;: {
        \&quot;library_path\&quot;: \&quot;&quot; nvidia-driver-package &quot;/lib/libEGL_nvidia.so.0\&quot;,
        \&quot;api_version\&quot; : \&quot;1.4.312\&quot;
    }
}&quot;)
    (file-append mesa &quot;/share/vulkan/icd.d&quot;))
</code></pre>
</div>

 <p>
If we have with-nvidia? flag passed, we create an ICD in Guix store with a path to nvidia-driver-package. It is necessary, because ICD provided in driver package doesn't work for headless setup (and in my case beefy PC is headless, so it is necessary). In case we run without Nvidia proprietary driver, we fallback to Mesa ICDs.
</p></li>

 <li> <p>
As we are using least-authority-wrapper for an enhanced security, it is necessary to specify all the devices we will need access to:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(device-mappings
 (append
  (list (file-system-mapping
          (source &quot;/dev/dri&quot;)
          (target source)
          (writable? #t)))
  (if with-nvidia?
      (map (lambda (dev)
             (file-system-mapping
               (source dev)
               (target source)
               (writable? #t)))
           '(&quot;/dev/nvidiactl&quot;
             &quot;/dev/nvidia0&quot;
             &quot;/dev/nvidia-modeset&quot;
             &quot;/dev/nvidia-uvm&quot;
             &quot;/dev/nvidia-uvm-tools&quot;))
      '())))
</code></pre>
</div>

 <p>
We always mount /dev/dri and, in case of Nvidia, specify additional Nvidia devices.
</p></li>

 <li> <p>
Finally we create least-authority-wrapper itself. It is pretty straightforward: just getting all file mappings together and specifying environment variables we want to save for the running service:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(llama-server
 (least-authority-wrapper
  (file-append llama-cpp &quot;/bin/llama-server&quot;)
  #:name &quot;llama-server&quot;
  #:user user
  #:group group
  #:preserved-environment-variables
  (append %default-preserved-environment-variables
          '(&quot;SSL_CERT_FILE&quot; &quot;VK_ICD_FILENAMES&quot; &quot;HOME&quot;))
  #:mappings
  (append
   (list (file-system-mapping
           (source &quot;/etc/ssl/certs/ca-certificates.crt&quot;)
           (target source)
           (writable? #f))
         (file-system-mapping
           (source %llama-home)
           (target source)
           (writable? #t))
         (file-system-mapping
           (source (if with-nvidia? nvidia-driver-package mesa))
           (target source)
           (writable? #f))
         (file-system-mapping
           (source icd)
           (target source)
           (writable? #f)))
   device-mappings)
  #:namespaces
  (fold delq spaces '(net user))))
</code></pre>
</div>

 <p>
SSL certificates are needed for llama.cpp to be able to access HuggingFace via HTTPS. Also, ICD path and the whole Mesa or Nvidia driver path in Guix store are mapped, so all the Vulkan drivers stuff could be loaded.
</p></li>
</ol></div>
</div>
 <div id="outline-container-org256a8ca" class="outline-3">
 <h3 id="org256a8ca">What to run?</h3>
 <div class="outline-text-3" id="text-org256a8ca">
 <p>
As we have the service in place, we now can use it in OS configuration. Here is an example:
</p>

 <div class="org-src-container">
 <pre class="src src-scheme"> <code>(use-modules ...
             (rodion services llama))

(operating-system
  (services (list ...
                  (service llama-cpp-service-type
                           (llama-cpp-configuration
                            (huggingface-name &quot;unsloth/Qwen3.5-27B-GGUF:UD-Q4_K_XL&quot;)
                            (parameters (list &quot;-c&quot; &quot;64000&quot;
                                              &quot;--temp&quot; &quot;0.6&quot;
                                              &quot;--top-k&quot; &quot;20&quot;
                                              &quot;--top-p&quot; &quot;0.95&quot;
                                              &quot;--min-p&quot; &quot;0.00&quot;))
                            (host &quot;0.0.0.0&quot;)
                            (with-nvidia? #t)
                            (nvidia-driver-package nvidia-driver-595))))))
</code></pre>
</div>

 <p>
In my case machine cannot be accessed from the Internet (only via the VPN), so 0.0.0.0 host seems to be a rather safe choice.
</p>

 <p>
When the model had been downloaded, you can access UI via the  <a href="http://&lt;your-ip&gt;:8080/">http://&lt;your-ip&gt;:8080/</a> to give your newly deployed model a try.
</p>

 <p>
Of course, OpenAI-compatible API is also available on the same host and port and could be used with a wide range of tools, i.e. with Claude Code, gpt.el in Emacs or my favorite editor-agnostic  <a href="https://eca.dev/">ECA</a>.
</p>

 <p>
Model I am currently running is  <a href="https://huggingface.co/unsloth/Qwen3.5-27B-GGUF">Qwen3.5-27B</a> with 64000 context. It can be used for light coding and asking questions, as well as tool calling with speed ~32 t/s on RTX 4090.
</p>

 <p>
For now I am delighted with results, stay tuned and happy hacking.
</p>

 <p>
P.S. If you have any questions/patches on the little-guix-channel or this llama-cpp-service-type in particular, don't hesitate to write an email on rodion [at] goritskov.com
</p>
</div>
</div>
</div>
</div>]]></description>
  <category>guix</category>
  <category>llm</category>  <link>https://goritskov.com/posts/run_your_own_part_1_llama_server.html</link>
  <guid isPermaLink="false">https://goritskov.com/posts/run_your_own_part_1_llama_server.html</guid>
  <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
</item>
</channel>
</rss>
