<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sid Probstein</title>
    <description>The latest articles on DEV Community by Sid Probstein (@sidswirl).</description>
    <link>https://dev.to/sidswirl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1132432%2F063b263f-a2ce-4db8-b6ff-b96cf0655d3c.jpeg</url>
      <title>DEV Community: Sid Probstein</title>
      <link>https://dev.to/sidswirl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sidswirl"/>
    <language>en</language>
    <item>
      <title>Moving Docker images between repos with crane (after imagetools wasted my afternoon)</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Thu, 11 Jun 2026 17:55:15 +0000</pubDate>
      <link>https://dev.to/sidswirl/moving-docker-images-between-repos-with-crane-after-imagetools-wasted-my-afternoon-bjg</link>
      <guid>https://dev.to/sidswirl/moving-docker-images-between-repos-with-crane-after-imagetools-wasted-my-afternoon-bjg</guid>
      <description>&lt;p&gt;I had a freshly-built, multi-arch dev image (&lt;code&gt;linux/amd64&lt;/code&gt; + &lt;code&gt;linux/arm64&lt;/code&gt;) and one job: promote it into a private partner repo on Docker Hub, plus stamp a dated tag so &lt;code&gt;:latest&lt;/code&gt; is always traceable back to a real build. Cross-repo. Should be five minutes.&lt;/p&gt;

&lt;p&gt;It was not five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;The obvious tool is &lt;code&gt;docker buildx imagetools create&lt;/code&gt;... it's built for copying manifests between tags. So I reached for it. And it sat there. Then it 400'd. Then I retried, and it hung. Cross-repo blob copies on Docker Hub are reproducibly flaky with imagetools, and I burned the better part of an hour confirming that before I went looking for something else.&lt;/p&gt;

&lt;p&gt;The fallback most people reach for next is worse: pull the image down, retag it, push it to the new repo. That round-trips the &lt;em&gt;entire&lt;/em&gt; image — every layer, every arch - through your laptop's daemon and disk, only to push the same bits back up. And if you're not careful with how you tag, you flatten a multi-arch index down to whatever single arch your machine happens to be. No thanks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnetefv8hsa8yrjqd3ix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxnetefv8hsa8yrjqd3ix.png" alt=" " width="799" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why crane
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/google/go-containerregistry/tree/main/cmd/crane" rel="noopener noreferrer"&gt;&lt;code&gt;crane&lt;/code&gt;&lt;/a&gt; (from Google's go-containerregistry) does the copy &lt;strong&gt;registry-to-registry&lt;/strong&gt;. It never pulls the image to your machine.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It preserves the multi-arch manifest.&lt;/strong&gt; &lt;code&gt;crane cp&lt;/code&gt; copies the whole image &lt;em&gt;index&lt;/em&gt; by digest. Both arches come along. Nothing gets flattened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It actually works cross-repo on Docker Hub.&lt;/strong&gt; Where imagetools 400'd and hung, crane did the server-to-server copy in seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No daemon, no disk.&lt;/strong&gt; It talks to the registries directly. Your laptop just orchestrates.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    subgraph slow["pull → tag → push"]
        A[source repo&amp;lt;br/&amp;gt;you/app:dev] --&amp;gt; L[your daemon + disk&amp;lt;br/&amp;gt;whole image] --&amp;gt; B[partner repo&amp;lt;br/&amp;gt;you/partner-app:latest]
    end
    subgraph crane["crane cp"]
        C[source repo&amp;lt;br/&amp;gt;you/app:dev] --&amp;gt;|manifest + blobs&amp;lt;br/&amp;gt;by digest| D[partner repo&amp;lt;br/&amp;gt;you/partner-app:latest]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run it as a container, no install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; gcr.io/go-containerregistry/crane:debug &amp;lt;crane-args&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole installation story. Nothing on the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth issues (MacOS)
&lt;/h2&gt;

&lt;p&gt;crane in the container reads &lt;code&gt;~/.docker/config.json&lt;/code&gt;. On &lt;strong&gt;macOS with Docker Desktop&lt;/strong&gt;, your login isn't &lt;em&gt;in&lt;/em&gt; that file — it's &lt;code&gt;credsStore: osxkeychain&lt;/code&gt;, sitting in the macOS keychain. So if you naively mount &lt;code&gt;~/.docker/config.json&lt;/code&gt; into the container, crane sees no usable credential and hands you &lt;code&gt;UNAUTHORIZED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fix: pull the credential out of the keychain on the host, write it into a &lt;em&gt;temporary inline&lt;/em&gt; config, mount that, and delete it the moment you're done. Never print it, never commit it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'rm -rf "$TMP"'&lt;/span&gt; EXIT
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/mkcfg.py"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import json, sys, base64
d = json.load(sys.stdin)
auth = base64.b64encode((d["Username"] + ":" + d["Secret"]).encode()).decode()
json.dump({"auths": {"https://index.docker.io/v1/": {"auth": auth}}}, open(sys.argv[1], "w"))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"https://index.docker.io/v1/"&lt;/span&gt; | docker-credential-osxkeychain get | python3 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/mkcfg.py"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/config.json"&lt;/span&gt;

CRANE&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TMP&lt;/span&gt;&lt;span class="s2"&gt;/config.json"&lt;/span&gt;:/root/.docker/config.json:ro &lt;span class="se"&gt;\&lt;/span&gt;
            gcr.io/go-containerregistry/crane:debug &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;trap ... EXIT&lt;/code&gt; cleans up the temp config when your shell exits, so the credential doesn't linger.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;Linux&lt;/strong&gt; (or anywhere &lt;code&gt;docker login&lt;/code&gt; writes inline creds), skip all of that and mount &lt;code&gt;~/.docker/config.json&lt;/code&gt; directly. And obvious-but-worth-saying: you need a &lt;strong&gt;read/write&lt;/strong&gt; login on the destination. A read-only token can't push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promote the image
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy SRC -&amp;gt; DST, full multi-arch manifest, server to server:&lt;/span&gt;
CRANE &lt;span class="nb"&gt;cp &lt;/span&gt;you/app:dev &lt;span class="se"&gt;\&lt;/span&gt;
         you/partner-app:latest

&lt;span class="c"&gt;# also stamp a dated, immutable tag so :latest is always traceable to a build:&lt;/span&gt;
CRANE &lt;span class="nb"&gt;cp &lt;/span&gt;you/app:dev &lt;span class="se"&gt;\&lt;/span&gt;
         you/partner-app:dev-2026-06-11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two copies, both registry-side, both done before you can refill your coffee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification
&lt;/h2&gt;

&lt;p&gt;Don't trust that the copy worked. Prove it. The destination digest must equal the source digest — same bits, same manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CRANE digest you/app:dev                 &lt;span class="c"&gt;# source&lt;/span&gt;
CRANE digest you/partner-app:latest      &lt;span class="c"&gt;# must equal source&lt;/span&gt;
CRANE digest you/partner-app:dev-2026-06-11   &lt;span class="c"&gt;# must equal source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those three lines match, the right image landed under both tags. If they don't, you copied the wrong thing — better to find out here than in a partner's deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CRANE &lt;span class="nb"&gt;ls &lt;/span&gt;you/partner-app                          &lt;span class="c"&gt;# list tags&lt;/span&gt;
CRANE manifest you/app:dev                         &lt;span class="c"&gt;# full manifest JSON, see the arches&lt;/span&gt;
CRANE tag you/app@sha256:&amp;lt;digest&amp;gt; newtag           &lt;span class="c"&gt;# add a tag to an existing digest&lt;/span&gt;
CRANE copy &amp;lt;SRC&amp;gt; &amp;lt;DST&amp;gt;                             &lt;span class="c"&gt;# alias of cp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat worth knowing: tag &lt;em&gt;deletion&lt;/em&gt; isn't a crane operation. Use the Docker Hub UI or the API for that (and it needs an admin-scoped token — a read/write PAT won't delete).&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Reach for crane any time you're moving an image &lt;em&gt;between registries or repos&lt;/em&gt; and you care about the manifest arriving intact — promotions, mirrors, cross-org handoffs. It skips the daemon, skips the disk, and it doesn't fall over on Docker Hub cross-repo copies the way imagetools does. And whatever you do, build the &lt;code&gt;crane digest&lt;/code&gt; source-equals-dest check into the workflow. It costs two seconds.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>resources</category>
    </item>
    <item>
      <title>Running PyTorch fork-safe in Celery on macOS</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:23:46 +0000</pubDate>
      <link>https://dev.to/sidswirl/running-pytorch-fork-safe-in-celery-on-macos-4h6a</link>
      <guid>https://dev.to/sidswirl/running-pytorch-fork-safe-in-celery-on-macos-4h6a</guid>
      <description>&lt;p&gt;If you've ever seen this in your Celery logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Process 'ForkPoolWorker-7' pid:32839 exited with 'signal 11 (SIGSEGV)'
billiard.exceptions.WorkerLostError:
    Worker exited prematurely: signal 11 (SIGSEGV) Job: 0.

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and the macOS crash report buries the real message in a JSON blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"asi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"CoreFoundation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*** multi-threaded process forked ***"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"libsystem_c.dylib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"crashed on child side of fork pre-exec"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...you've hit one of the classic fork-after-init traps. Here's what's going on and how to actually fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line fix (if you're in a hurry)
&lt;/h2&gt;

&lt;p&gt;Set these env vars before the Celery worker's MainProcess imports anything heavy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# &amp;lt;your-project&amp;gt;/celery.py ... the very first thing the worker imports
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENBLAS_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OMP_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MKL_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NUMEXPR_NUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VECLIB_MAXIMUM_THREADS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OBJC_DISABLE_INITIALIZE_FORK_SAFETY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YES&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first set forces every BLAS library to single-threaded mode. VECLIB_MAXIMUM_THREADS is the one most people forget; it covers Apple's Accelerate framework, which is what PyTorch uses by default on Apple Silicon. The last one tells the Objective-C runtime to skip its fork-safety abort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;PyTorch's nn.Linear on macOS arm64 calls into Apple Accelerate, which does its parallel matmuls via libdispatch (Grand Central Dispatch).&lt;/p&gt;

&lt;p&gt;The first BLAS call lazily spins up a pool of libdispatch worker queues in the calling process.&lt;/p&gt;

&lt;p&gt;If that "calling process" is your Celery worker's MainProcess (say, because something during boot does a tiny matmul: spaCy preload, an embedding warmup, anything that imports numpy and runs a real op), those queues now live in the parent. &lt;/p&gt;

&lt;p&gt;When the prefork pool then fork()s a child, the child inherits broken queue handles. The next BLAS call from inside the child dereferences a stale pointer and you get the SIGSEGV.&lt;/p&gt;

&lt;p&gt;The stack trace in the crash report makes it unambiguous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0: _dispatch_apply_with_attr_f      (libdispatch)
1: dispatch_apply_with_attr         (libdispatch)
3: cblas_sgemm                      (Accelerate)
5: at::native::cpublas::gemm        (libtorch_cpu)
6: at::native::addmm_impl_cpu_      (libtorch_cpu)
7: at::native::linear               (libtorch_cpu)
8: torch::autograd::THPVariable_linear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What doesn't work
&lt;/h2&gt;

&lt;p&gt;"Just lazy-load the model in the child." Even if you defer from_pretrained until you're inside a forked child, that first call still hits Accelerate BLAS, and the dispatch queues your child inherited from the parent are already broken.&lt;/p&gt;

&lt;p&gt;"Just bypass sentence_transformers.CrossEncoder.predict() and use bare-torch." Same story. Whether you go through CrossEncoder or call AutoModelForSequenceClassification directly, the SIGSEGV is one frame down inside linear().&lt;/p&gt;

&lt;p&gt;"Just don't import torch at the top of the module." Necessary but not sufficient. In our case, removing import torch from ai_provider.py was real progress, but then we discovered litellm transitively pulls torch the first time you call it. Every "warmup" preload that touched litellm still poisoned the parent. You have to audit every code path that runs before the first fork.&lt;/p&gt;

&lt;h2&gt;
  
  
  The defensive pattern that does work
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Defer heavy imports. Don't import torch at module top in anything that's part of the Celery autodiscovery chain. Push it into the function that needs it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bad ... taints anyone who imports this module
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Good ... torch only loads in workers that actually rerank
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;no_grad&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Gate "warmup" preloads off the Celery worker. Preloading models at startup makes sense for an ASGI server like Daphne. It is actively harmful in a forking Celery worker, because the warmup runs in MainProcess:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAppConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;is_celery_worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;celery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;worker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_celery_worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_preload_cross_encoder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What about Linux / Docker?
&lt;/h2&gt;

&lt;p&gt;Yes, this affects Linux too, just less dramatically. &lt;/p&gt;

&lt;p&gt;OpenBLAS and MKL both spin up thread pools on first use that don't survive fork; the typical Linux failure mode is a hang or a deadlock rather than a SIGSEGV. &lt;/p&gt;

&lt;p&gt;The good news: the same *_NUM_THREADS=1 env vars are the fix. &lt;/p&gt;

&lt;p&gt;VECLIB_MAXIMUM_THREADS and OBJC_DISABLE_INITIALIZE_FORK_SAFETY are no-ops on Linux, so the snippet above is portable. The deferred-import and gate-off-warmup patterns apply unchanged.&lt;/p&gt;

</description>
      <category>python</category>
      <category>celery</category>
    </item>
    <item>
      <title>The AI agent stack that’s quietly taking over enterprise workflows</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Sat, 03 May 2025 02:58:27 +0000</pubDate>
      <link>https://dev.to/sidswirl/the-ai-agent-stack-thats-quietly-taking-over-enterprise-workflows-2h8o</link>
      <guid>https://dev.to/sidswirl/the-ai-agent-stack-thats-quietly-taking-over-enterprise-workflows-2h8o</guid>
      <description>&lt;p&gt;Accenture, IBM, and AWS are all placing bets on &lt;a href="https://www.crewai.com/" rel="noopener noreferrer"&gt;Crew AI&lt;/a&gt;. Why? Because it makes building and deploying real AI agents possible.&lt;/p&gt;

&lt;p&gt;With Crew AI, teams are spinning up agents that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launch predictive marketing campaigns&lt;/li&gt;
&lt;li&gt;Automate financial back-office ops&lt;/li&gt;
&lt;li&gt;Optimize inventory and logistics&lt;/li&gt;
&lt;li&gt;And tackle 100+ other enterprise use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the catch: agents are only as good as the data they can reach. That’s where &lt;a href="https://swirlaiconnect.com/" rel="noopener noreferrer"&gt;SWIRL&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo016mxcb9nx03auythy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbo016mxcb9nx03auythy.png" alt=" " width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By pairing Crew AI with SWIRL, you get more than just agents—you get enterprise-ready, data-rich workflows that scale. No custom plumbing. No brittle integrations.&lt;/p&gt;

&lt;p&gt;With Crew AI + SWIRL, your agents can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to &lt;a href="https://swirlaiconnect.com/connectors" rel="noopener noreferrer"&gt;100+ enterprise data sources&lt;/a&gt; out-of-the-box&lt;/li&gt;
&lt;li&gt;Fetch the most relevant structured/unstructured data across silos&lt;/li&gt;
&lt;li&gt;Respect row-level permissions with real enterprise auth&lt;/li&gt;
&lt;li&gt;Summarize and answer with your LLM of choice&lt;/li&gt;
&lt;li&gt;Plug in easily via zero-code connectors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to see this in action? &lt;/p&gt;

&lt;p&gt;Message me for a demo or check the open source edition here: &lt;a href="https://github.com/swirlai/swirl-search" rel="noopener noreferrer"&gt;https://github.com/swirlai/swirl-search&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>showdev</category>
    </item>
    <item>
      <title>It was already indexed!</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Sun, 13 Aug 2023 16:48:52 +0000</pubDate>
      <link>https://dev.to/sidswirl/it-was-already-indexed-2b4</link>
      <guid>https://dev.to/sidswirl/it-was-already-indexed-2b4</guid>
      <description>&lt;p&gt;I recently had the pleasure of chatting with &lt;a class="mentioned-user" href="https://dev.to/dmitrykan"&gt;@dmitrykan&lt;/a&gt; on his  &lt;a href="https://dmitry-kan.medium.com/vector-podcast-e27d83ecd0be" rel="noopener noreferrer"&gt;Vector Podcast&lt;/a&gt;. Check it out: &lt;a href="https://dmitry-kan.medium.com/vector-podcast-with-sid-probstein-search-in-siloed-data-with-swirl-f2b9595a2715" rel="noopener noreferrer"&gt;https://dmitry-kan.medium.com/vector-podcast-with-sid-probstein-search-in-siloed-data-with-swirl-f2b9595a2715&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We talked about quite a few things, including: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The challenges of enterprise search in the post-cloud era&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How cross-silo search is particularlyt tricky because of entitlements (aka permissions) across silos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zero-code configuration of connectors in Swirl, where JSON path and developer API doc get the job done &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How large language models contextually re-rank disparate search results&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was an interesting twist at the end of the call. Dmitry uses a service called &lt;a href="https://clearword.com/" rel="noopener noreferrer"&gt;Clearword&lt;/a&gt; to transcribe recordings. Dmitry asked: “how quickly can you index the transcript and search it with Swirl?” &lt;/p&gt;

&lt;p&gt;Here is my answer: &lt;a href="https://www.youtube.com/watch?v=WMHnb_6Wf50" rel="noopener noreferrer"&gt;It was already indexed!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WMHnb_6Wf50"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Since there is no audio and it goes by quickly, let me explain ... Clearword emailed the transcript to both of us shortly after recording ended. It was indexed by Microsoft Outlook within seconds of arriving in my inbox. &lt;/p&gt;

&lt;p&gt;To verify this, I copied some text from the middle of the transcript and pasted it into Swirl, which returned the link to the email message with the transcript and the phrase I searched for. &lt;/p&gt;

&lt;p&gt;That simple truth - that the average enterprise is awash in search forms - is the entire reason metasearch is such a game-changing approach. Instead of making yet another repository, Swirl sends queries to existing search APIs and re-ranks the results from everything. It saves users a huge amount of time without a major IT project. &lt;/p&gt;

&lt;p&gt;Want to see for yourself? git it going with 2 commands via Docker here: &lt;a href="https://github.com/swirlai/swirl-search" rel="noopener noreferrer"&gt;https://github.com/swirlai/swirl-search&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Swirl 2.5 released</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Wed, 09 Aug 2023 14:37:03 +0000</pubDate>
      <link>https://dev.to/sidswirl/swirl-25-released-40g4</link>
      <guid>https://dev.to/sidswirl/swirl-25-released-40g4</guid>
      <description>&lt;p&gt;&lt;strong&gt;I am delighted to announce availability of Swirl 2.5!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi78nz5551etm2kuo0j5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi78nz5551etm2kuo0j5v.png" alt=" " width="800" height="542"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This version focused on performance. Configured with 12 SearchProviders, Swirl 2.5 supports ~15 queries/second on a Standard F16s v2 server (16 vcpus, 32 GiB memory) with a median response time of ~3 seconds. &lt;/p&gt;

&lt;p&gt;Version 2.5 also includes SearchProviders for HubSpot contact, company, and deal records, plus improvements to the Galaxy search UI (shown above).&lt;/p&gt;

&lt;p&gt;Check out the Release notes for full details: &lt;a href="https://github.com/swirlai/swirl-search/releases/tag/v2.5.0" rel="noopener noreferrer"&gt;https://github.com/swirlai/swirl-search/releases/tag/v2.5.0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Swirl?&lt;/strong&gt; A new open source metasearch engine; it queries anything with an API then uses spaCy to re-rank the unified results without copying any data! Includes zero-code configs for Apache Solr, ChatGPT, Elastic Search, OpenSearch, PostgreSQL, Google BigQuery, RequestsGet, Google PSE, NLResearch.com, Miro, Microsoft 365, HubSpot, Atlassian, YouTrack, GitHub &amp;amp; more! &lt;/p&gt;

</description>
      <category>search</category>
      <category>python</category>
      <category>django</category>
    </item>
    <item>
      <title>I wrote a metasearch engine called Swirl</title>
      <dc:creator>Sid Probstein</dc:creator>
      <pubDate>Thu, 03 Aug 2023 18:48:40 +0000</pubDate>
      <link>https://dev.to/sidswirl/i-wrote-a-metasearch-engine-called-swirl-23i9</link>
      <guid>https://dev.to/sidswirl/i-wrote-a-metasearch-engine-called-swirl-23i9</guid>
      <description>&lt;p&gt;Hi all! &lt;a href="https://github.com/sidprobstein/swirl-search" rel="noopener noreferrer"&gt;Swirl&lt;/a&gt; sends queries to existing search engines, unifies the results and re-ranks them all using large language models. It solves cross-silo information access and search problems in a fraction of the time and effort required to copy, ingest and index data. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo4d13eov2335nmcxjy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo4d13eov2335nmcxjy3.png" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a brief video intro: &lt;a href="https://youtu.be/sfsBYyu6qDQ" rel="noopener noreferrer"&gt;https://youtu.be/sfsBYyu6qDQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Swirl was written in python atop the django/celery/redis stack with a choice of Sqlite3 or PostgreSQL back-ends. The source code is available under the Apache 2.0 license. The distribution includes zero-code configs for Apache Solr, ChatGPT, Elastic Search, OpenSearch, PostgreSQL, Google BigQuery, RequestsGet, Google PSE, NLResearch.com, Miro, Microsoft 365, Atlassian, YouTrack, GitHub, HubSpot &amp;amp; more. Plug-in your access tokens or use Microsoft 365 to login and users can stop searching and start Swirling!&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sidprobstein/swirl-search" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.swirl.today" rel="noopener noreferrer"&gt;Swirl website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://join.slack.com/t/swirlmetasearch/shared_invite/zt-1qk7q02eo-kpqFAbiZJGOdqgYVvR1sfw" rel="noopener noreferrer"&gt;Slack channel for support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We are seeking contributions and feedback from developers working on all kinds of search solutions... thanks!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl07sutgaxaulu25xvqm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl07sutgaxaulu25xvqm.jpg" alt="Image description" width="800" height="683"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>search</category>
      <category>cloudcomputing</category>
      <category>llm</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
