Cache (1/5): Add content-addressed local inference caches - #121
Conversation
58d6791 to
d1ca639
Compare
| params: list[Any], | ||
| ) -> pd.DataFrame: | ||
| if input_hashes is not None: | ||
| if not input_hashes: |
There was a problem hiding this comment.
Why there are two checks here for input_hashes?
There was a problem hiding this comment.
None means no input-hash filter, while [] means no rows, so the checks preserve different query semantics. I added this to the tests.
| str(row["benchmark"]), | ||
| str(row["instruction_id"]), | ||
| str(row["model_a"]), | ||
| str(row["model_b"]), |
There was a problem hiding this comment.
Note: For sample-wise this can be None. As we have discussed it better to make this more general
There was a problem hiding this comment.
Yes, made model_b nullable now.
b1156b8 to
52a4620
Compare
|
I think it is a great cache design but I have some concerns apart from what I wrote above. So what we are planning is to have a seperate return (
Path(store_root)
/ kind
/ quote(task, safe="")
/ quote(provider, safe="")
/ quote(model, safe="")
)Although this makes sense in general, I feel like it is too fragmented. For each task/provider/model we are creating a database. However instead we can create this for kind/task only. Recommendations / Questions1) Database BoundaryAs discussed above we have to think about the database design. We can store provider, model and descriptor as SQL data and just assume cache is stored at {store_root}/{kind}/{task}.db2) MetadataPerhaps we can also store descriptors seperately CREATE TABLE descriptors (
descriptor_id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
model TEXT NOT NULL,
descriptor_json TEXT NOT NULL
); CREATE TABLE completions (
descriptor_id TEXT NOT NULL,
input_hash TEXT NOT NULL,
input_text TEXT NOT NULL,
completion TEXT NOT NULL,
PRIMARY KEY (descriptor_id, input_hash)
);but this is not necessary. 3)We do # cache_sqlite.py:207-210
"INSERT OR REPLACE INTO completions VALUES (...)"3)Perhaps we can also put a versioning in cache if we want to apply migrations in the future. |
| with sqlite3.connect(temporary_db) as conn: | ||
| conn.execute(self.schema) | ||
| rows.to_sql(self.table, conn, if_exists="append", index=False) | ||
| os.replace(temporary_db, self.db_path) |
There was a problem hiding this comment.
We might need some handling and protection for the cache. Assuming we can start two jobs at the same time, while reading it from
frames.append(pd.read_sql(f"SELECT * FROM {self.table}", conn))if another process appends something to this table while
os.replace(temporary_db, self.db_path)executed, than information will be lost. Also
pd.read_sql(...)
pd.concat(...)
.sort_values(...)
.drop_duplicates(...)
rows.to_sql(...)these operations load both databases and rewrites every row. If we have a huge cache this is expensive. We can use
ATTACH DATABASE ? AS incoming;
INSERT INTO completions (...)
SELECT ...
FROM incoming.completions
ON CONFLICT(descriptor_id, input_hash) DO UPDATE ...;However this is not required and current is also enough (unless we use VERY large cache)
There was a problem hiding this comment.
Agreed, merge_from now uses an in-place transaction with ATTACH and ON CONFLICT, so it no longer rewrites or replaces the live database.
|
@kargibora I kept one database per role/task/provider/model/descriptor because each folder is also the inspection and synchronization unit, and separate files reduce writer contention. I'm not sure if this granularity has any downsides. Since each database already represents one descriptor, Let me know if you agree with this. |
Description
Adds content-addressed local SQLite caches for model completions and judge outputs.
Cache layout:
metadata.jsonstores the validated model descriptor. The databases use these schemas:top_logprobsis new relative to the earlier draft so judgements can restore the first-token logprobs used by #118 parsers.