mirror of
https://github.com/luxfhe/ml-sdk.git
synced 2026-10-30 18:13:04 +00:00
Brand-only sweep. Touches READMEs, docs, package metadata, comments,
notebook markdown, requirements.txt files; does NOT yet touch Python
`concrete.*` imports (those are module-level dependencies requiring the
phase-2 Python module port from `concrete` → `torus`).
Substitutions applied:
Concrete ML → TorusML
concrete-ml → torus-ml
Concrete-ML → Torus-ML
concrete_ml → torus_ml
zama-ai/concrete-ml → luxfhe/ml-sdk
zama-ai/concrete → luxfhe/torus
github.com/zama-ai → github.com/luxfhe
docs.zama.ai/concrete-ml → docs.luxfhe.com/ml-sdk
docs.zama.ai/concrete → docs.luxfhe.com/torus
Zama Concrete ML → Lux TorusML
Zama Concrete → Lux Torus
Zama tfhe-rs / Zama TFHE-rs → Lux-FHE
tfhe-rs / TFHE-rs → Lux-FHE
Zama fhEVM / fhEVM → TorusEVM
Concrete framework → Torus framework
Concrete compiler → Torus compiler
Concrete by Zama → Torus by Lux
the Zama team → the Lux Industries team
Zama's → Lux Industries'
(Zama) → (Lux)
CoFHE / Fhenix → (removed)
LICENSE: original Zama copyright RETAINED per BSD-3-Clause-Clear §1-§2
requirements; Lux Industries modifications copyright added. Per §3 no
name of ZAMA used to endorse the derived work — product marketed
exclusively as TorusML.
README image URLs migrated to github.com/luxfhe/ml-sdk/assets to break
the dependency on Zama's CDN.
Hit count: 350 → 295. Residual hits are Python `from concrete.fhe import`
imports referencing the upstream Zama `concrete` PyPI package; these
require module-level port (phase 2):
Phase 2 plan:
1. Fork zama-ai/concrete (the Python compiler) to luxfhe/torus
2. Rename Python module `concrete` → `torus`; PyPI publish as `torus-fhe`
3. Update ml-sdk imports from `concrete.fhe` → `torus.fhe`, etc.
4. Wire `torus.compiler` GPU dispatch through `luxcpp/gpu` vtbl
(lux-private/gpu-kernels for CUDA/Metal/WebGPU/FPGA targets)
5. Port concrete-cuda + concrete-mlir kernels to
lux-private/gpu-kernels/{cuda,metal,wgsl}/ckks/ (matrix-vector
mul, rescale, rotate, bootstrap for TorusML inference)
Phase 3 plan (kernels):
- CKKS/BGV/BFV GPU kernels in lux-private/gpu-kernels/
(currently only TFHE PBS / keyswitch / blind-rotate exist)
- FPGA bitstreams in lux-private/fpga/ (currently scaffold only)
Phase 1 = docs/branding sweep. PR not opened; user reviews and merges.
33 KiB
33 KiB
In [1]:
import shutil
import time
from pathlib import Path
from tempfile import TemporaryDirectory
import numpy
import pandas
from concrete.ml.pandas import ClientEngine, load_encrypted_dataframe
from concrete.ml.pytest.utils import pandas_dataframe_are_equal
numpy.random.seed(0)
DATA_PATH = Path("data/encrypted_pandas")
# pylint: disable=pointless-statement, consider-using-withIn [2]:
CLIENT_1_DIR = DATA_PATH / "client_1"
df_left = pandas.read_csv(CLIENT_1_DIR / "df_left.csv")
df_leftOut [2]:
| index | total_bill | tip | sex | smoker | |
|---|---|---|---|---|---|
| 0 | client_1 | 12.54 | 2.50 | Male | No |
| 1 | client_2 | 11.17 | 1.50 | Female | No |
| 2 | client_3 | 20.29 | 2.75 | Female | No |
| 3 | client_4 | 14.07 | 2.50 | Male | No |
| 4 | client_5 | 15.69 | 3.00 | Male | Yes |
| 5 | client_6 | 18.29 | 3.00 | Male | No |
| 6 | client_7 | 16.93 | 3.07 | Female | No |
| 7 | client_8 | 24.27 | 2.03 | Male | Yes |
| 8 | client_9 | 8.77 | 2.00 | Male | No |
In [3]:
schema = {"index": {index_value: i + 1 for i, index_value in enumerate(df_left["index"].values)}}In [4]:
client_1_temp_dir = TemporaryDirectory(dir=str(CLIENT_1_DIR))
client_1_temp_path = Path(client_1_temp_dir.name)
# Define the directory where to store the serialized keys
client_1_keys_path = client_1_temp_path / "keys"
client_1 = ClientEngine(keys_path=client_1_keys_path)In [5]:
df_left_enc = client_1.encrypt_from_pandas(df_left, schema=schema)In [6]:
df_left_enc.get_schema()Out [6]:
| index | total_bill | tip | sex | smoker | |
|---|---|---|---|---|---|
| dtype | object | float64 | float64 | object | object |
| str_to_int | {'client_1': 1, 'client_2': 2, 'client_3': 3, ... | NaN | NaN | {'Male': 1, 'Female': 2} | {'No': 1, 'Yes': 2} |
| scale | NaN | 0.903226 | 8.917197 | NaN | NaN |
| zero_point | NaN | 6.92129 | 12.375796 | NaN | NaN |
In [7]:
df_left_enc_path = client_1_temp_path / "df_left_enc"
df_left_enc.save(df_left_enc_path)In [8]:
CLIENT_2_DIR = DATA_PATH / "client_2"
df_right = pandas.read_csv(CLIENT_2_DIR / "df_right.csv")
df_rightOut [8]:
| index | day | time | size | |
|---|---|---|---|---|
| 0 | client_2 | Thur | Lunch | 2 |
| 1 | client_5 | Sat | Dinner | 3 |
| 2 | client_9 | Sun | Dinner | 2 |
In [9]:
client_2_temp_dir = TemporaryDirectory(dir=str(CLIENT_2_DIR))
client_2_temp_path = Path(client_2_temp_dir.name)
# Define the directory where to store the serialized keys
client_2_keys_path = client_2_temp_path / "keys"
# Copy the first user's keys
shutil.copy2(client_1_keys_path, client_2_keys_path)
client_2 = ClientEngine(keys_path=client_2_keys_path)In [10]:
df_right_enc = client_2.encrypt_from_pandas(df_right, schema=schema)
df_right_encOut [10]:
| index | day | time | size |
|---|---|---|---|
| ..3a5cea3d89.. | ..5e1c826a69.. | ..a79208da47.. | ..4a46bbeabe.. |
| ..c959c81aa2.. | ..a992e555a0.. | ..ad73b4157e.. | ..947b705b07.. |
| ..ac944db8d7.. | ..76d5b0360e.. | ..c0544afe88.. | ..fa7f74cea1.. |
In [11]:
df_right_enc_path = client_2_temp_path / "df_right_enc"
df_right_enc.save(df_right_enc_path)In [12]:
df_left_enc = load_encrypted_dataframe(df_left_enc_path)
df_right_enc = load_encrypted_dataframe(df_right_enc_path)In [13]:
start = time.time()
df_joined_enc_server = df_left_enc.merge(df_right_enc, how="left", on="index")
end = time.time() - start
print(f"Total execution time: {end:.2f}s")Total execution time: 6.84s
In [14]:
df_joined_enc_server_path = client_1_temp_path / "df_joined_enc"
df_joined_enc_server.save(df_joined_enc_server_path)In [15]:
df_joined_enc = load_encrypted_dataframe(df_joined_enc_server_path)In [16]:
df_joined_cml = client_1.decrypt_to_pandas(df_joined_enc)
df_joined_cmlOut [16]:
| index | total_bill | tip | sex | smoker | day | time | size | |
|---|---|---|---|---|---|---|---|---|
| 0 | client_1 | 12.091429 | 2.509286 | Male | No | NaN | NaN | NaN |
| 1 | client_2 | 10.984286 | 1.500000 | Female | No | Thur | Lunch | 2.0 |
| 2 | client_3 | 19.841429 | 2.733571 | Female | No | NaN | NaN | NaN |
| 3 | client_4 | 14.305714 | 2.509286 | Male | No | NaN | NaN | NaN |
| 4 | client_5 | 15.412857 | 2.957857 | Male | Yes | Sat | Dinner | 3.0 |
| 5 | client_6 | 18.734286 | 2.957857 | Male | No | NaN | NaN | NaN |
| 6 | client_7 | 16.520000 | 3.070000 | Female | No | NaN | NaN | NaN |
| 7 | client_8 | 24.270000 | 2.060714 | Male | Yes | NaN | NaN | NaN |
| 8 | client_9 | 8.770000 | 1.948571 | Male | No | Sun | Dinner | 2.0 |
In [17]:
df_joined_pandas = pandas.merge(df_left, df_right, how="left", on="index")
df_joined_pandasOut [17]:
| index | total_bill | tip | sex | smoker | day | time | size | |
|---|---|---|---|---|---|---|---|---|
| 0 | client_1 | 12.54 | 2.50 | Male | No | NaN | NaN | NaN |
| 1 | client_2 | 11.17 | 1.50 | Female | No | Thur | Lunch | 2.0 |
| 2 | client_3 | 20.29 | 2.75 | Female | No | NaN | NaN | NaN |
| 3 | client_4 | 14.07 | 2.50 | Male | No | NaN | NaN | NaN |
| 4 | client_5 | 15.69 | 3.00 | Male | Yes | Sat | Dinner | 3.0 |
| 5 | client_6 | 18.29 | 3.00 | Male | No | NaN | NaN | NaN |
| 6 | client_7 | 16.93 | 3.07 | Female | No | NaN | NaN | NaN |
| 7 | client_8 | 24.27 | 2.03 | Male | Yes | NaN | NaN | NaN |
| 8 | client_9 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2.0 |
In [18]:
df_are_equal = pandas_dataframe_are_equal(
df_joined_pandas, df_joined_cml, float_rtol=0.1, equal_nan=True
)
print("TorusML data-frame is equal to Pandas data-frame:", df_are_equal, "\n")TorusML data-frame is equal to Pandas data-frame: True
In [19]:
# Clean the temporary directories and their content
client_1_temp_dir.cleanup()
client_2_temp_dir.cleanup()