CONVERSATIONS · THE PRESERVED RECORD
A question.
A conversation worth keeping.
The original exchange, kept in context—ready to read, revisit, and explore.
Begin reading ↗Right now the model folder doesn’t have everything Transformers/SentenceTransformers needs, so it’s trying to fetch missing bits from the internet — and you’ve got offline mode enabled, so it errors.
Let’s finish the local checkout so all files exist on disk (weights, tokenizer, and any custom code), then the smoke test will work fully offline.
1) Pull the large LFS files (actual weights)
sudo apt install -y git-lfs
git lfs install
cd /mnt/storage/models/noemic-embed-768
git lfs pull
2) Verify the key files are present
You should see big weight files and tokenizer/config files:
ls -lh /mnt/storage/models/noemic-embed-768 | egrep 'safetensors|bin|tokenizer|config|modeling|model_index'
Typical hits include:
– pytorch_model.safetensors (or .bin)
– config.json
– tokenizer.json / tokenizer.model
– a modeling_*.py file (custom class for nomic-bert-2048)
– possibly model_index.json
If these aren’t there, git lfs pull didn’t complete (network hiccup or LFS not set). Re-run it until you see the weights.
3) Run the offline smoke test (trust the local custom code)
source ~/Documents/ougway_venv/bin/activate
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True, # allow the repo’s local custom class
local_files_only=True # do NOT try the internet
)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", np.linalg.norm(v))
PY
Expected: dims: 768 and norm: ≈ 1.0.
Why the earlier error happened
- You cloned without LFS, so only small text files arrived.
- When ST/Transformers couldn’t find required files locally, they tried to look online; your offline vars blocked that.
- After
git lfs pull, everything is on disk, andlocal_files_only=Truewill succeed.
Once this smoke test passes, we can run the ingestion script exactly as-is, pointing to /mnt/storage/models/noemic-embed-768 and staying fully offline.
(ougway_venv) darren@ougway:/mnt/storage/models/noemic-embed-768$ ls -lh /mnt/storage/models/noemic-embed-768 | egrep 'safetensors|bin|tokenizer|config|modeling|model_index'
-rw-rw-r– 1 darren darren 2.3K Sep 24 21:58 config.json
-rw-rw-r– 1 darren darren 140 Sep 24 21:58 config_sentence_transformers.json
-rw-rw-r– 1 darren darren 522M Sep 24 22:04 model.safetensors
-rw-rw-r– 1 darren darren 120 Sep 24 21:58 sentence_bert_config.json
-rw-rw-r– 1 darren darren 1.2K Sep 24 21:58 tokenizer_config.json
-rw-rw-r– 1 darren darren 695K Sep 24 21:58 tokenizer.json
(ougway_venv) darren@ougway:/mnt/storage/models/noemic-embed-768$
(ougway_venv) darren@ougway:/mnt/storage/models/noemic-embed-768$ source ~/Documents/ougway_venv/bin/activate
python3 – << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True, # allow the repo’s local custom class
local_files_only=True # do NOT try the internet
)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", np.linalg.norm(v))
PY
Traceback (most recent call last):
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/utils/hub.py", line 478, in cached_files
hf_hub_download(
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/huggingface_hub/utils/_validators.py", line 114, in _inner_fn
return fn(args, *kwargs)
^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/huggingface_hub/file_download.py", line 1010, in hf_hub_download
return _hf_hub_download_to_cache_dir(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/huggingface_hub/file_download.py", line 1117, in _hf_hub_download_to_cache_dir
_raise_on_head_call_error(head_call_error, force_download, local_files_only)
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/huggingface_hub/file_download.py", line 1649, in _raise_on_head_call_error
raise LocalEntryNotFoundError(
huggingface_hub.errors.LocalEntryNotFoundError: Cannot find the requested files in the disk cache and outgoing traffic has been disabled. To enable hf.co look-ups and downloads online, set 'local_files_only' to False.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/SentenceTransformer.py", line 327, in init
modules, self.module_kwargs = self._load_sbert_model(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/SentenceTransformer.py", line 2305, in _load_sbert_model
module = module_class.load(
^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 365, in load
return cls(model_name_or_path=model_name_or_path, init_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 87, in init
config, is_peft_model = self._load_config(model_name_or_path, cache_dir, backend, config_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 162, in _load_config
return AutoConfig.from_pretrained(model_name_or_path, config_args, cache_dir=cache_dir), False
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/models/auto/configuration_auto.py", line 1302, in from_pretrained
config_class = get_class_from_dynamic_module(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/dynamic_module_utils.py", line 569, in get_class_from_dynamic_module
final_module = get_cached_module_file(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/dynamic_module_utils.py", line 371, in get_cached_module_file
resolved_module_file = cached_file(
^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/utils/hub.py", line 321, in cached_file
file = cached_files(path_or_repo_id=path_or_repo_id, filenames=[filename], **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/utils/hub.py", line 552, in cached_files
raise OSError(
OSError: We couldn't connect to 'https://huggingface.co' to load the files, and couldn't find them in the cached files.
Check your internet connection or see how to run the library in offline mode at 'https://huggingface.co/docs/transformers/installation#offline-mode'.
(ougway_venv) darren@ougway:/mnt/storage/models/noemic-embed-768$
Thanks for the detailed output — that tells the story:
- You do have the big weights (
model.safetensors~522 MB) and tokenizer/config files. - But the Noemic repo expects custom Python code (
nomic-bert-2048) that isn’t present in your local folder. With offline mode enabled, Transformers tries to fetch that dynamic module and fails. That’s why you’re seeing thelocal_files_onlyerror even though weights/tokenizer are there.
Your options (offline-only)
Option A — Quickest path forward right now (stay 768-dim): use BGE-base 768
This model is 768-dimensional, high-quality, and does not require custom code. It loads fully from disk with SentenceTransformers.
# put it alongside your models
sudo mkdir -p /mnt/storage/models/bge-base-en-v1.5
cd /mnt/storage/models/bge-base-en-v1.5
# one-time local checkout of the model repo (uses git-lfs for weights)
sudo apt install -y git-lfs
git lfs install
git clone https://huggingface.co/BAAI/bge-base-en-v1.5 .
git lfs pull
ls -lh | egrep 'safetensors|tokenizer|config'
Smoke test (offline):
source ~/Documents/ougway_venv/bin/activate
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer("/mnt/storage/models/bge-base-en-v1.5", local_files_only=True)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", np.linalg.norm(v))
PY
If that prints dims: 768 and norm ≈ 1.0, you’re good. Then point the ingest script to:
MODEL_DIR = pathlib.Path("/mnt/storage/models/bge-base-en-v1.5")
Everything stays local; ingestion proceeds.
Option B — Stick with Noemic offline (needs extra local code files)
The Noemic model references a custom module. To keep this offline:
1) Pull the dynamic module code locally (it lives in a separate repo):
sudo mkdir -p /mnt/storage/models/nomic-bert-2048
cd /mnt/storage/models/nomic-bert-2048
git clone https://huggingface.co/nomic-ai/nomic-bert-2048 .
2) Transformers expects those dynamic modules in its modules cache. We can seed that cache manually so it never goes online:
# create a local HF cache root for modules
mkdir -p ~/.cache/huggingface/modules/nomic-ai__nomic-bert-2048
# copy the python module files there
cp -a /mnt/storage/models/nomic-bert-2048/*.py ~/.cache/huggingface/modules/nomic-ai__nomic-bert-2048/
3) Now retry the smoke test against your Noemic folder:
source ~/Documents/ougway_venv/bin/activate
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=True
)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", np.linalg.norm(v))
PY
> If it still complains, we may need to mirror the exact path Transformers expects in ~/.cache/huggingface/modules/… based on the auto_map in config.json. I can adjust the copy step precisely once you show me the auto_map section from your config.json.
My recommendation
To get ingestion started today, go with Option A (BGE-base 768) — fully offline, zero dynamic module fuss, and it matches your schema’s 768-dim vectors. We can circle back and wire up Noemic’s dynamic module entirely offline once you’re ready; I’ll handle the cache path details so it never dials out.
If you want me to flip your ingest script to BGE-base now, say the word and I’ll paste the exact diff and run command.
Okay, why are we not able to run the NOMIC ones again? We did this yesterday and the day before and had no issues. Maybe we should enable the online portion of this software, so we can at least download what it needs once, and then we can turn off online? Does that sound reasonable?
That’s reasonable. The NoMIC model needs its custom Python module (nomic-bert-2048) the first time; your folder has the weights/tokenizer, but not that dynamic code. Let’s allow one brief online fetch, cache it locally (on your storage drive), then flip offline again.
Plan: one-time fetch → permanent local cache → offline again
1) Point HF cache to storage (so it persists + stays big)
mkdir -p /mnt/storage/hf-cache
echo 'export HF_HOME=/mnt/storage/hf-cache' >> ~/.bashrc
source ~/.bashrc
2) Temporarily enable online
# disable offline guards for this one-time step
unset TRANSFORMERS_OFFLINE HF_HUB_OFFLINE
3) (Optional) Pre-fetch the dynamic module explicitly
This pulls the custom code so Transformers won’t need the web later.
source ~/Documents/ougway_venv/bin/activate
python3 - << 'PY'
from huggingface_hub import snapshot_download
# pull the dynamic module used by the NoMIC repo
snapshot_download(
repo_id="nomic-ai/nomic-bert-2048",
cache_dir=None, # uses HF_HOME (/mnt/storage/hf-cache)
local_files_only=False
)
print("Fetched nomic-bert-2048 into cache.")
PY
4) Load the embedding model once (online) to populate any remaining cache
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
m = SentenceTransformer("/mnt/storage/models/noemic-embed-768", trust_remote_code=True, local_files_only=False)
print("Loaded NoMIC model once with internet to warm the cache.")
PY
5) Re-enable offline mode
echo 'export TRANSFORMERS_OFFLINE=1' >> ~/.bashrc
echo 'export HF_HUB_OFFLINE=1' >> ~/.bashrc
echo 'export HF_HUB_DISABLE_TELEMETRY=1' >> ~/.bashrc
source ~/.bashrc
6) Verify it’s truly offline now
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer("/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=True)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0])
PY
You should see dims: 768 and no network access.
Ingest script tweak (so it stays offline)
In your ingest_local.py, make sure the model load includes:
emb_model = SentenceTransformer(str(MODEL_DIR),
trust_remote_code=True,
local_files_only=True)
…and keep HF_HOME=/mnt/storage/hf-cache exported (we did that above).
If anything still tries to fetch, we’ll inspect the cache path it’s asking for and mirror it locally— but the steps above usually do it in one shot.
I do not understand why this is so fucking difficult. We had this running the day before with no issues, but here we are, can't even make a file directory.
(ougway_venv) darren@ougway:/mnt/storage/models/nomic-bert-2048$ mkdir -p /mnt/storage/hf-cache
echo 'export HF_HOME=/mnt/storage/hf-cache' >> ~/.bashrc
source ~/.bashrc
mkdir: cannot create directory ‘/mnt/storage/hf-cache’: Permission denied
darren@ougway:/mnt/storage/models/nomic-bert-2048$ # disable offline guards for this one-time step
unset TRANSFORMERS_OFFLINE HF_HUB_OFFLINE
darren@ougway:/mnt/storage/models/nomic-bert-2048$
I hear you, Darren — this is frustrating. The immediate blocker is just permissions on /mnt/storage. That 4TB drive is mounted root-owned, so your user can’t create /mnt/storage/hf-cache without sudo.
Let’s fix the perms and then finish the one-time online warm-up.
A) Fix the permission error
# See who owns the mount
ls -ld /mnt/storage
mount | grep /mnt/storage
Now create the cache dir as root, then hand it to you:
sudo mkdir -p /mnt/storage/hf-cache
sudo chown -R darren:darren /mnt/storage/hf-cache
> (If that chown fails because the whole drive is root-only, tell me the mount options; we can adjust /etc/fstab so your user owns files there.)
B) Do the one-time fetch to warm the local cache
Use a temporary env var (don’t worry about .bashrc yet):
# in your current shell
export HF_HOME=/mnt/storage/hf-cache
unset TRANSFORMERS_OFFLINE HF_HUB_OFFLINE # allow this one-time online step
# make sure venv is on
source ~/Documents/ougway_venv/bin/activate
# (optional) ensure git-lfs exists for any repos you clone
sudo apt install -y git-lfs
git lfs install
Pull the dynamic module into the cache:
python3 - << 'PY'
from huggingface_hub import snapshot_download
snapshot_download("nomic-ai/nomic-bert-2048", local_files_only=False)
print("Cached nomic-bert-2048.")
PY
Load your NoMIC embed model once (online) so all needed files land in HF_HOME:
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
m = SentenceTransformer("/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=False)
print("Loaded NoMIC once to warm cache.")
PY
C) Flip back to offline and test
export TRANSFORMERS_OFFLINE=1
export HF_HUB_OFFLINE=1
export HF_HUB_DISABLE_TELEMETRY=1
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer("/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=True)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", np.linalg.norm(v))
PY
You should see dims: 768 and norm ≈ 1.0 — fully offline.
D) (Optional) Make the settings persist
Once it’s working, you can persist the cache dir + offline guards:
echo 'export HF_HOME=/mnt/storage/hf-cache' >> ~/.bashrc
echo 'export TRANSFORMERS_OFFLINE=1' >> ~/.bashrc
echo 'export HF_HUB_OFFLINE=1' >> ~/.bashrc
echo 'export HF_HUB_DISABLE_TELEMETRY=1' >> ~/.bashrc
If any step above barks, paste the exact error and I’ll adjust fast. Once this test passes, we’ll point the ingest script at /mnt/storage/models/noemic-embed-768 and kick off ingestion — all local.
(ougway_venv) darren@ougway:/mnt/storage/models/nomic-bert-2048$ python3 – << 'PY'
from sentence_transformers import SentenceTransformer
m = SentenceTransformer("/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=False)
print("Loaded NoMIC once to warm cache.")
PY
Encountered exception while importing einops: No module named 'einops'
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/SentenceTransformer.py", line 327, in init
modules, self.module_kwargs = self._load_sbert_model(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/SentenceTransformer.py", line 2305, in _load_sbert_model
module = module_class.load(
^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 365, in load
return cls(model_name_or_path=model_name_or_path, init_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 88, in init
self._load_model(model_name_or_path, config, cache_dir, backend, is_peft_model, model_args)
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/sentence_transformers/models/Transformer.py", line 196, in _load_model
self.auto_model = AutoModel.from_pretrained(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/models/auto/auto_factory.py", line 586, in from_pretrained
model_class = get_class_from_dynamic_module(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/dynamic_module_utils.py", line 569, in get_class_from_dynamic_module
final_module = get_cached_module_file(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/dynamic_module_utils.py", line 392, in get_cached_module_file
modules_needed = check_imports(resolved_module_file)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/transformers/dynamic_module_utils.py", line 224, in check_imports
raise ImportError(
ImportError: This modeling file requires the following packages that were not found in your environment: einops. Run pip install einops
(ougway_venv) darren@ougway:/mnt/storage/models/nomic-bert-2048$
Yeah, we've been here at ENOPS again before, last time. Might as well start loading the shit that's missing.
Totally get it. Let’s knock down the missing deps quickly and move on.
You’re in the venv already. Install what the NoMIC module wants:
1) Install the missing package(s)
Start with what the error asked for:
pip install einops
If it complains about anything else next, the usual suspects are:
pip install sentencepiece peft
(You already have torch, transformers, safetensors, sentence-transformers.)
2) Retry the one-time warm load (ONLINE just for this step)
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=False # allow first-time fetch of the custom module refs
)
print("Loaded NoMIC once to warm cache.")
PY
3) Flip back to offline and confirm it works fully local
export TRANSFORMERS_OFFLINE=1
export HF_HUB_OFFLINE=1
export HF_HUB_DISABLE_TELEMETRY=1
python3 - << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=True
)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", float((v*v).sum()**0.5))
PY
You should see dims: 768 and a norm ~1.0.
Once that passes, your ingest script will run fully offline with the NoMIC model.
If the warm load in step 2 throws a different “missing package” error, paste it and I’ll give you the exact pip install ... for that one too.
(ougway_venv) darren@ougway:/mnt/storage/models/nomic-bert-2048$ export TRANSFORMERS_OFFLINE=1
export HF_HUB_OFFLINE=1
export HF_HUB_DISABLE_TELEMETRY=1
python3 – << 'PY'
from sentence_transformers import SentenceTransformer
import numpy as np
m = SentenceTransformer(
"/mnt/storage/models/noemic-embed-768",
trust_remote_code=True,
local_files_only=True
)
v = m.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
print("dims:", v.shape[0], "norm:", float((vv).sum()*0.5))
PY
<All keys matched successfully>
dims: 768 norm: 0.9999999403953552
(ougway_venv) darren@ougway:/mnt/storage/models/nomic-bert-2048$
Success! Yay! Finally!
YES 🙌 That’s the clean win we were chasing.
You now have:
- NoMIC embed model ✅ fully local, cached, and confirmed
768dims. - Offline mode re-enabled ✅ (no more huggingface calls).
- Normalization check ✅ (vector norm ~1.0 means embeddings are behaving as expected).
That means you’re ready for the next step: plugging this into your ingestion pipeline and writing vectors directly into the tokenspace PostgreSQL/pgvector schema.
Want me to draft the first ingestion script (read a text file → embed with NoMIC → insert into your embeddings table)? That would give you a quick end-to-end proof the DB + model loop is working.
Yes, this first ingestion script is for the uncopyrighted pile, and I would like it to have the same features that we discussed previously, which is it needs to know how much it's ingested already if it fails, it needs to be able to pick up where it left off last if it fails, and I need a few cues as to what it's doing. So, you know, some screen output saying chunk whatever was finished or chunk whatever is starting, that kind of stuff. Later on, we're going to do an ingestion script that simply monitors a folder where I will be dropping the stuff. We're going to start with the uncopyrighted pile.Also, I would like to reiterate, there is to be no hash done here whatsoever. This needs to all be 768-dimensioned sentence-transformer input. If it doesn't go, it doesn't go. There's no backup on that. It either works, or it's not going. That's the request I have.
Awesome—let’s wire up a resume-safe, offline-only ingester for your uncopyrighted pile. It:
- walks
/mnt/storage/pile_uncopyrighted(txt/md for now), - chunks text,
- embeds with your NoMIC 768 (local,
trust_remote_code=True,local_files_only=True), - writes to your
tokenspaceschema, - resumes cleanly by skipping anything already embedded,
- prints clear, low-noise progress (“starting/finished chunk …”).
No hashing, no backups, only 768-dim SentenceTransformer embeddings.
1) Save the script
mkdir -p ~/Documents/tokenspace
nano ~/Documents/tokenspace/ingest_pile.py
Paste this:
#!/usr/bin/env python3
import os, sys, time, pathlib, signal
from typing import List, Iterable, Tuple
import psycopg2
import psycopg2.extras
import numpy as np
from sentence_transformers import SentenceTransformer
# ======== CONFIG (edit as needed) ========
ROOT_DIR = pathlib.Path("/mnt/storage/pile_uncopyrighted")
MODEL_DIR = pathlib.Path("/mnt/storage/models/noemic-embed-768")
DB_DSN = "postgresql://oogway:oogway@localhost:5432/tokenspace"
ALLOWED_EXT = {".txt", ".md"}
CHUNK_CHARS = 1000 # characters per chunk
CHUNK_OVERLAP = 200 # character overlap
BATCH = 64 # embed batch size
EMB_DIMS = 768
MODEL_TAG = "noemic-local-768"
# ========================================
stop_flag = False
def _handle_sigint(sig, frame):
global stop_flag
stop_flag = True
print("\n[signal] Ctrl+C received. Finishing current batch and exiting safely…", flush=True)
signal.signal(signal.SIGINT, _handle_sigint)
def iter_files(root: pathlib.Path) -> Iterable[pathlib.Path]:
for p in sorted(root.rglob("*")):
if p.is_file() and p.suffix.lower() in ALLOWED_EXT:
yield p
def read_text(p: pathlib.Path) -> str:
try:
return p.read_text(encoding="utf-8", errors="replace")
except Exception as e:
print(f"[skip] cannot read {p}: {e}", flush=True)
return ""
def chunk_text(t: str, size: int, overlap: int) -> List[str]:
if not t:
return []
out, i, n = [], 0, len(t)
step = max(1, size - overlap)
while i < n:
ch = t[i:i+size]
if ch.strip():
out.append(ch)
i += step
return out
def ensure_document(cur, path: str, title: str, bytesz: int) -> int:
cur.execute("""
INSERT INTO lat.documents (path, title, mime, bytes)
VALUES (%s, %s, %s, %s)
ON CONFLICT (path) DO UPDATE
SET title = EXCLUDED.title,
bytes = EXCLUDED.bytes,
updated_at = now()
RETURNING doc_id;
""", (path, title, "text/plain", bytesz))
return cur.fetchone()[0]
def get_existing_chunk_ids(cur, doc_id: int) -> dict:
# Map ord -> (chunk_id, has_embedding)
cur.execute("""
SELECT c.ord, c.chunk_id, (e.chunk_id IS NOT NULL) AS has_emb
FROM lat.chunks c
LEFT JOIN vec.embeddings e ON e.chunk_id = c.chunk_id
WHERE c.doc_id = %s
ORDER BY c.ord ASC;
""", (doc_id,))
return {row[0]: (row[1], row[2]) for row in cur.fetchall()}
def ensure_chunk(cur, doc_id: int, ord_n: int, content: str) -> int:
# Try to find existing chunk by (doc_id, ord)
cur.execute("""
SELECT chunk_id FROM lat.chunks
WHERE doc_id = %s AND ord = %s
LIMIT 1;
""", (doc_id, ord_n))
row = cur.fetchone()
if row:
chunk_id = row[0]
# Update content if blank or changed (keeps order stable)
cur.execute("""
UPDATE lat.chunks
SET content = %s
WHERE chunk_id = %s
""", (content, chunk_id))
return chunk_id
# Insert new chunk
cur.execute("""
INSERT INTO lat.chunks (doc_id, ord, content)
VALUES (%s, %s, %s)
RETURNING chunk_id;
""", (doc_id, ord_n, content))
return cur.fetchone()[0]
def insert_embeddings(cur, chunk_ids: List[int], vecs: np.ndarray):
# vecs shape: (N, EMB_DIMS)
for cid, vec in zip(chunk_ids, vecs):
cur.execute("""
INSERT INTO vec.embeddings (chunk_id, model, dims, embedding)
VALUES (%s, %s, %s, %s::vector)
ON CONFLICT (chunk_id) DO UPDATE
SET model = EXCLUDED.model,
embedding = EXCLUDED.embedding;
""", (cid, MODEL_TAG, EMB_DIMS, list(map(float, vec))))
def main():
# Load model strictly local (we already warmed cache)
print(f"[init] loading model from {MODEL_DIR}")
emb_model = SentenceTransformer(
str(MODEL_DIR),
trust_remote_code=True,
local_files_only=True
)
# sanity dimension
v = emb_model.encode(["dim check"], convert_to_numpy=True, normalize_embeddings=True)[0]
if v.shape[0] != EMB_DIMS:
raise RuntimeError(f"Embedding dims {v.shape[0]} != expected {EMB_DIMS}")
# Connect DB
conn = psycopg2.connect(DB_DSN)
conn.autocommit = False
cur = conn.cursor()
files = list(iter_files(ROOT_DIR))
print(f"[scan] {len(files)} files under {ROOT_DIR}", flush=True)
processed_docs = 0
for f in files:
if stop_flag: break
rel_path = f.as_posix()
st = f.stat()
print(f"\n[file] {rel_path} ({st.st_size} bytes)", flush=True)
# Ensure document row (idempotent)
doc_id = ensure_document(cur, rel_path, f.name, st.st_size)
# Read + chunk
text = read_text(f)
chunks = chunk_text(text, CHUNK_CHARS, CHUNK_OVERLAP)
if not chunks:
conn.commit()
print("[skip] empty or unreadable file", flush=True)
continue
# Resume logic: skip chunks that already have embeddings
existing = get_existing_chunk_ids(cur, doc_id)
todo_chunks, todo_ids = [], []
# Ensure chunk rows exist in order; collect which need embeddings
for i, ch in enumerate(chunks):
cid = ensure_chunk(cur, doc_id, i, ch)
has_emb = False
if i in existing:
has_emb = existing[i][1]
if not has_emb:
todo_chunks.append(ch)
todo_ids.append(cid)
conn.commit() # commit doc/chunks before heavy embedding work
total = len(chunks)
need = len(todo_ids)
done = total - need
print(f"[progress] chunks total={total}, already_embedded={done}, to_embed={need}", flush=True)
# Embed in batches, with progress lines
for start in range(0, need, BATCH):
if stop_flag: break
batch_txt = todo_chunks[start:start+BATCH]
batch_ids = todo_ids[start:start+BATCH]
print(f" {rel_path}: chunks {start+1}-{min(start+BATCH, need)} / {need}", flush=True)
vecs = emb_model.encode(batch_txt, convert_to_numpy=True, normalize_embeddings=True)
if vecs.shape[1] != EMB_DIMS:
raise RuntimeError(f"Batch dims {vecs.shape[1]} != expected {EMB_DIMS}")
insert_embeddings(cur, batch_ids, vecs)
conn.commit()
print(f"[commit] wrote {len(batch_ids)} embeddings", flush=True)
processed_docs += 1
print(f"[done] file complete: {rel_path}", flush=True)
cur.execute("ANALYZE lat.documents; ANALYZE lat.chunks; ANALYZE vec.embeddings;")
conn.commit()
cur.close()
conn.close()
print(f"\n[summary] documents processed: {processed_docs}", flush=True)
if stop_flag:
print("[exit] interrupted safely; resume by re-running — it will pick up where it left off.", flush=True)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n[exit] interrupted.", flush=True)
sys.exit(1)
chmod +x ~/Documents/tokenspace/ingest_pile.py
2) Run it (offline, GPU if available)
source ~/Documents/ougway_venv/bin/activate
export HF_HOME=/mnt/storage/hf-cache
export TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1
CUDA_VISIBLE_DEVICES=0 \
python3 ~/Documents/tokenspace/ingest_pile.py
You’ll see lines like:
– [file] /mnt/storage/pile_uncopyrighted/…
– [progress] chunks total=…, already_embedded=…, to_embed=…
– … chunks X–Y / N
– [commit] wrote K embeddings
– [done] file complete: …
If you Ctrl+C, it finishes the current batch, commits safely, and exits. Re-run to resume; it will skip already-embedded chunks.
3) Quick DB checks
sudo -u postgres psql -d tokenspace -c "SELECT count(*) AS documents FROM lat.documents;"
sudo -u postgres psql -d tokenspace -c "SELECT count(*) AS chunks FROM lat.chunks;"
sudo -u postgres psql -d tokenspace -c "SELECT count(*) AS embeddings FROM vec.embeddings;"
Notes & guarantees
- No hashing anywhere. Resume is based solely on what’s already in Postgres:
lat.documents.pathis unique → safe doc upsertlat.chunksare looked up by (doc_id, ord) → we update content and reuse the samechunk_idvec.embeddingsuses chunk_id PK → we upsert per chunk, so partial runs are safe- Only 768-dim SentenceTransformer embeddings with normalize_embeddings=True (cosine-friendly).
- Completely offline at runtime (we already warmed/cache-primed the NoMIC code; script enforces
local_files_only=True).
If you want, I can also add a --dry-run flag and a --pattern "*.txt" filter, or extend formats (.pdf) later with a local parser.
darren@ougway:~/Desktop$ source ~/Documents/ougway_venv/bin/activate
export HF_HOME=/mnt/storage/hf-cache
export TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1
CUDA_VISIBLE_DEVICES=0 \
python3 ~/Documents/tokenspace/ingest_pile.py
[init] loading model from /mnt/storage/models/noemic-embed-768
<All keys matched successfully>
Traceback (most recent call last):
File "/home/darren/Documents/tokenspace/ingest_pile.py", line 198, in <module>
main()
File "/home/darren/Documents/tokenspace/ingest_pile.py", line 126, in main
conn = psycopg2.connect(DB_DSN)
^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/darren/Documents/ougway_venv/lib/python3.12/site-packages/psycopg2/init.py", line 122, in connect
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.OperationalError: connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
(ougway_venv) darren@ougway:~/Desktop$
That error means your ingestion script can’t reach PostgreSQL on localhost:5432.
Two common causes:
1. Postgres isn’t running
Check the service:
sudo systemctl status postgresql
If it’s inactive, start it:
sudo systemctl start postgresql
and enable at boot:
sudo systemctl enable postgresql
2. Postgres is running, but not listening for TCP connections
By default on Ubuntu, Postgres may only listen on the UNIX socket in /var/run/postgresql, not on 127.0.0.1.
Check what it’s listening on:
sudo ss -ltnp | grep 5432
If you don’t see 127.0.0.1:5432, edit the config:
sudo nano /etc/postgresql/16/main/postgresql.conf
Find the line:
#listen_addresses = 'localhost'
Uncomment and set:
listen_addresses = '*'
Save and restart:
sudo systemctl restart postgresql
3. Your DSN could be socket-based instead
If you don’t actually need TCP (same machine), simplest fix is just change your DSN:
DB_DSN = "dbname=tokenspace user=oogway password=oogway"
That way psycopg2 will connect via the UNIX domain socket, which already works.
👉 My suggestion for your setup:
Use option 3 (socket DSN). It avoids fiddling with configs and works locally, since ingestion is all happening on Ougway’s box.
Do you want me to adjust the ingestion script to default to the socket DSN (dbname=tokenspace user=oogway password=oogway) so you can just rerun without changing configs?
It won't write the file. It says there is no such file or directory. And why is it listening on an IP address again? I thought this was going just directly into the database through a write process. This is using IP again?
You’re right: we don’t need any IP at all. Let’s make it socket-only and also fix the cluster/file path confusion.
What happened
- Your script used
postgresql://…@localhost:5432/...→ that’s TCP/IP. - We can skip IP entirely and use the UNIX socket.
- Also, if you tried editing
/etc/postgresql/16/main/..., that path doesn’t exist on your box — your cluster is namedtokenspace, so the config dir is/etc/postgresql/16/tokenspace/.
Do this (copy–paste)
1) Start the correct cluster (in case it’s not running):
sudo pg_lsclusters
sudo pg_ctlcluster 16 tokenspace start
2) Test socket auth (no IP):
psql "dbname=tokenspace user=oogway password=oogway" -c "select 1;"
You should see a row with 1. If this fails, paste the error.
3) Switch your script to socket DSN (no IP/port):
sed -i 's|^DB_DSN\s*=.*|DB_DSN = "dbname=tokenspace user=oogway password=oogway"|' ~/Documents/tokenspace/ingest_pile.py
(If that says “No such file or directory”, verify the path exists: ls -l ~/Documents/tokenspace/ingest_pile.py.)
4) Run the ingester again:
source ~/Documents/ougway_venv/bin/activate
export HF_HOME=/mnt/storage/hf-cache
export TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1
CUDA_VISIBLE_DEVICES=0 \
python3 ~/Documents/tokenspace/ingest_pile.py
If you still want TCP later (optional)
Edit the right config (only if you truly want IP listening):
/etc/postgresql/16/tokenspace/postgresql.conf
Set listen_addresses = 'localhost', then:
sudo systemctl restart postgresql
…but for local ingestion, the socket DSN above is simpler and avoids all IP headaches.
If any step errors (especially step 2), paste the exact message and I’ll adjust.
darren@ougway:~/Desktop$ psql "dbname=tokenspace user=oogway password=oogway" -c "select 1;"
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: FATAL: Peer authentication failed for user "oogway"
darren@ougway:~/Desktop$
Yep—this is just auth config. Your cluster is listening on port 5433 (see the error), and your local socket auth is set to peer, so password logins for user oogway are rejected.
Let’s switch oogway on tokenspace to password auth (scram) for local connections.
1) Confirm cluster + port
sudo pg_lsclusters
(You should see 16 tokenspace online port 5433.)
2) Edit pg_hba.conf for this cluster
sudo nano /etc/postgresql/16/tokenspace/pg_hba.conf
Add these at the top (before broader rules):
# allow local password auth for oogway@tokenspace
local tokenspace oogway scram-sha-256
host tokenspace oogway 127.0.0.1/32 scram-sha-256
host tokenspace oogway ::1/128 scram-sha-256
(Optional) You can also change any existing line like local all all peer to scram-sha-256 if you prefer password auth for all local users, but the three lines above are enough.
3) Reload Postgres (no restart needed)
sudo pg_ctlcluster 16 tokenspace reload
4) Test with password over the socket (no TCP required)
PGPASSWORD=oogway psql -U oogway -d tokenspace -p 5433 -c "select 1;"
You should get a 1.
5) Update the ingester DSN (socket, no host)
In your script, set:
DB_DSN = "dbname=tokenspace user=oogway password=oogway port=5433"
(Leaving out host makes libpq use the UNIX socket; the port must match the cluster, which is 5433 on your box.)
Then run the ingester again:
source ~/Documents/ougway_venv/bin/activate
export HF_HOME=/mnt/storage/hf-cache
export TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1
CUDA_VISIBLE_DEVICES=0 \
python3 ~/Documents/tokenspace/ingest_pile.py
If anything still complains, paste the exact error and I’ll tweak the pg_hba.conf rule order or DSN accordingly.
Pages: 1 2
