mostlyai-engine 2.3.3__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. mostlyai_engine-2.5.0/LICENSE_HEADER +13 -0
  2. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/PKG-INFO +9 -9
  3. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/README.md +1 -1
  4. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/__init__.py +1 -1
  5. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_common.py +6 -3
  6. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/character.py +3 -1
  7. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/datetime.py +6 -5
  8. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/engine/vllm_engine.py +10 -8
  9. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/xgrammar_utils.py +11 -2
  10. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/argn.py +5 -2
  11. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/common.py +1 -1
  12. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/generation.py +7 -5
  13. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/probability.py +13 -9
  14. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/pyproject.toml +11 -11
  15. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/.gitignore +0 -0
  16. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/LICENSE +0 -0
  17. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_dtypes.py +0 -0
  18. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/__init__.py +0 -0
  19. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/language/__init__.py +0 -0
  20. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/language/categorical.py +0 -0
  21. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/language/datetime.py +0 -0
  22. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/language/numeric.py +0 -0
  23. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/language/text.py +0 -0
  24. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/__init__.py +0 -0
  25. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/categorical.py +0 -0
  26. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/itt.py +0 -0
  27. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/lat_long.py +0 -0
  28. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_encoding_types/tabular/numeric.py +0 -0
  29. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/__init__.py +0 -0
  30. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/common.py +0 -0
  31. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/encoding.py +0 -0
  32. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/engine/__init__.py +0 -0
  33. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/engine/base.py +0 -0
  34. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/engine/hf_engine.py +0 -0
  35. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/generation.py +0 -0
  36. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/interface.py +0 -0
  37. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/lstm.py +0 -0
  38. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/tokenizer_utils.py +0 -0
  39. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_language/training.py +0 -0
  40. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_memory.py +0 -0
  41. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/__init__.py +0 -0
  42. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/encoding.py +0 -0
  43. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/fairness.py +0 -0
  44. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/interface.py +0 -0
  45. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_tabular/training.py +0 -0
  46. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_training_utils.py +0 -0
  47. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/_workspace.py +0 -0
  48. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/analysis.py +0 -0
  49. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/domain.py +0 -0
  50. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/encoding.py +0 -0
  51. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/generation.py +0 -0
  52. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/logging.py +0 -0
  53. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/random_state.py +0 -0
  54. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/splitting.py +0 -0
  55. {mostlyai_engine-2.3.3 → mostlyai_engine-2.5.0}/mostlyai/engine/training.py +0 -0
@@ -0,0 +1,13 @@
1
+ Copyright 2025 MOSTLY AI
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyai-engine
3
- Version: 2.3.3
3
+ Version: 2.5.0
4
4
  Summary: Synthetic Data Engine
5
5
  Project-URL: homepage, https://github.com/mostly-ai/mostlyai-engine
6
6
  Project-URL: repository, https://github.com/mostly-ai/mostlyai-engine
@@ -8,6 +8,7 @@ Project-URL: documentation, https://mostly-ai.github.io/mostlyai-engine/
8
8
  Author-email: MOSTLY AI <dev@mostly.ai>
9
9
  License-Expression: Apache-2.0
10
10
  License-File: LICENSE
11
+ License-File: LICENSE_HEADER
11
12
  Classifier: Development Status :: 5 - Production/Stable
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: Intended Audience :: Financial and Insurance Industry
@@ -17,13 +18,12 @@ Classifier: Intended Audience :: Science/Research
17
18
  Classifier: Intended Audience :: Telecommunications Industry
18
19
  Classifier: License :: OSI Approved :: Apache Software License
19
20
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3.10
21
21
  Classifier: Programming Language :: Python :: 3.11
22
22
  Classifier: Programming Language :: Python :: 3.12
23
23
  Classifier: Programming Language :: Python :: 3.13
24
24
  Classifier: Topic :: Software Development :: Libraries
25
25
  Classifier: Typing :: Typed
26
- Requires-Python: >=3.10
26
+ Requires-Python: <3.14,>=3.11
27
27
  Requires-Dist: accelerate>=1.5.0
28
28
  Requires-Dist: datasets>=3.0.0
29
29
  Requires-Dist: huggingface-hub[hf-xet]>=0.30.2
@@ -38,15 +38,15 @@ Requires-Dist: pyarrow>=16.0.0
38
38
  Requires-Dist: scikit-learn>=1.4.0
39
39
  Requires-Dist: setuptools>=77.0.3
40
40
  Requires-Dist: tokenizers>=0.21.0
41
- Requires-Dist: torch<2.9.0,>=2.8.0
42
- Requires-Dist: torchaudio<2.9.0,>=2.8.0
43
- Requires-Dist: torchvision<0.24.0,>=0.23.0
44
- Requires-Dist: transformers>=4.55.0
41
+ Requires-Dist: torch<2.10.0,>=2.9.0
42
+ Requires-Dist: torchaudio<2.10.0,>=2.9.0
43
+ Requires-Dist: torchvision<0.25.0,>=0.24.0
44
+ Requires-Dist: transformers<5,>=4.55.0
45
45
  Requires-Dist: xgrammar>=0.1.21
46
46
  Provides-Extra: gpu
47
47
  Requires-Dist: bitsandbytes==0.42.0; (sys_platform == 'darwin') and extra == 'gpu'
48
48
  Requires-Dist: bitsandbytes>=0.45.5; (sys_platform == 'linux') and extra == 'gpu'
49
- Requires-Dist: vllm==0.10.2; (sys_platform == 'linux' or sys_platform == 'darwin') and extra == 'gpu'
49
+ Requires-Dist: vllm==0.12.0; (sys_platform == 'linux' or sys_platform == 'darwin') and extra == 'gpu'
50
50
  Description-Content-Type: text/markdown
51
51
 
52
52
  # Synthetic Data Engine 💎
@@ -116,7 +116,7 @@ uv pip install -U 'mostlyai-engine[gpu]'
116
116
  On Linux, one can explicitly install the CPU-only variant of torch together with `mostlyai-engine`:
117
117
 
118
118
  ```bash
119
- uv pip install -U torch==2.8.0+cpu torchvision==0.23.0+cpu mostlyai-engine --extra-index-url https://download.pytorch.org/whl/cpu
119
+ uv pip install -U torch==2.9.1+cpu torchvision==0.24.1+cpu mostlyai-engine --extra-index-url https://download.pytorch.org/whl/cpu
120
120
  ```
121
121
 
122
122
  ## TabularARGN for Flat Data
@@ -65,7 +65,7 @@ uv pip install -U 'mostlyai-engine[gpu]'
65
65
  On Linux, one can explicitly install the CPU-only variant of torch together with `mostlyai-engine`:
66
66
 
67
67
  ```bash
68
- uv pip install -U torch==2.8.0+cpu torchvision==0.23.0+cpu mostlyai-engine --extra-index-url https://download.pytorch.org/whl/cpu
68
+ uv pip install -U torch==2.9.1+cpu torchvision==0.24.1+cpu mostlyai-engine --extra-index-url https://download.pytorch.org/whl/cpu
69
69
  ```
70
70
 
71
71
  ## TabularARGN for Flat Data
@@ -34,7 +34,7 @@ __all__ = [
34
34
  "TabularARGN",
35
35
  "LanguageModel",
36
36
  ]
37
- __version__ = "2.3.3"
37
+ __version__ = "2.5.0"
38
38
 
39
39
  # suppress specific warning related to os.fork() in multi-threaded processes
40
40
  warnings.filterwarnings("ignore", category=DeprecationWarning, message=".*multi-threaded.*fork.*")
@@ -273,8 +273,9 @@ def safe_convert_datetime(values: pd.Series, date_only: bool = False) -> pd.Seri
273
273
  utc=True,
274
274
  dayfirst=False, # assume 1/3/2020 is Jan 3
275
275
  )
276
- # check whether firstday=True yields less non-NA, and if so, switch to using that flag
277
- if values_parsed_fixed.isna().sum() > values.isna().sum():
276
+ has_slash_dates = values.astype("string").str.contains(r"^\s*\d{1,2}/\d{1,2}/\d{4}(?:\s|$)", regex=True).any()
277
+ # some mixed-format slash dates are interpreted more reliably with dayfirst=True
278
+ if has_slash_dates and values_parsed_fixed.isna().sum() > values.isna().sum():
278
279
  values_parsed_fixed_dayfirst = pd.to_datetime(
279
280
  values,
280
281
  errors="coerce", # silently map invalid dates to NA
@@ -937,6 +938,7 @@ def impute_from_non_nan_distribution(values: pd.Series, column_stats: dict) -> t
937
938
  Returns:
938
939
  tuple[pd.Series, pd.Series]: The series with imputed values and the mask of NaNs.
939
940
  """
941
+ values = values.copy()
940
942
  nan_mask = values.isna()
941
943
  vc = values.value_counts(normalize=True)
942
944
  if vc.empty:
@@ -944,7 +946,8 @@ def impute_from_non_nan_distribution(values: pd.Series, column_stats: dict) -> t
944
946
  probs = vc.values
945
947
  categories = vc.index
946
948
  # NOTE: an alternative will be to use the largest remainder method
947
- values[nan_mask] = np.random.choice(categories, size=nan_mask.sum(), p=probs)
949
+ if nan_mask.any():
950
+ values.loc[nan_mask] = np.random.choice(categories, size=nan_mask.sum(), p=probs)
948
951
  return values, nan_mask.astype(int)
949
952
 
950
953
 
@@ -102,7 +102,9 @@ def encode_character(values: pd.Series, stats: dict, _: pd.Series | None = None)
102
102
  df_split = split_sub_columns_character(values, max_string_length)
103
103
  for idx in range(max_string_length):
104
104
  sub_col = f"P{idx}"
105
- np_codes = np.array(pd.Categorical(df_split[sub_col], categories=stats["codes"][sub_col]).codes)
105
+ categories = list(stats["codes"][sub_col].keys())
106
+ values_at_pos = df_split[sub_col].where(df_split[sub_col].isin(categories), UNKNOWN_TOKEN)
107
+ np_codes = np.array(pd.Categorical(values_at_pos, categories=categories).codes)
106
108
  np.place(np_codes, np_codes == -1, 0)
107
109
  df_split[sub_col] = np_codes
108
110
  if stats["has_nan"]:
@@ -234,9 +234,10 @@ def decode_datetime(df_encoded: pd.DataFrame, stats: dict):
234
234
  d = df_encoded["day"] + stats["min_values"]["day"]
235
235
  # fix invalid dates by setting these to last day of month
236
236
  is_leap = y.apply(lambda x: calendar.isleap(x))
237
- d[is_leap & (m == 2) & (d > 29)] = 29
238
- d[~is_leap & (m == 2) & (d > 28)] = 28
239
- d[((m == 4) | (m == 6) | (m == 9) | (m == 11)) & (d > 30)] = 30
237
+ d = d.copy()
238
+ d.loc[is_leap & (m == 2) & (d > 29)] = 29
239
+ d.loc[~is_leap & (m == 2) & (d > 28)] = 28
240
+ d.loc[((m == 4) | (m == 6) | (m == 9) | (m == 11)) & (d > 30)] = 30
240
241
  # concatenate to datetime string
241
242
  y = y.astype(str)
242
243
  m = m.astype(str).str.zfill(2)
@@ -271,7 +272,7 @@ def decode_datetime(df_encoded: pd.DataFrame, stats: dict):
271
272
  # set all values to NaN if no valid values were present
272
273
  values[df_encoded["nan"] == 0] = pd.NA
273
274
  # convert from string to datetime
274
- values = pd.to_datetime(values)
275
+ values = pd.to_datetime(values).astype("datetime64[ns]")
275
276
  if not stats["has_time"]:
276
- values = pd.to_datetime(values.dt.date)
277
+ values = pd.to_datetime(values.dt.date).astype("datetime64[ns]")
277
278
  return values
@@ -27,11 +27,10 @@ from peft import PeftConfig
27
27
  from pydantic import BaseModel
28
28
  from transformers import AutoConfig, AutoTokenizer
29
29
  from vllm import LLM, SamplingParams
30
- from vllm.config import _get_and_verify_max_len
31
30
  from vllm.distributed import cleanup_dist_env_and_memory
32
31
  from vllm.inputs.data import TokensPrompt
33
32
  from vllm.lora.request import LoRARequest
34
- from vllm.sampling_params import GuidedDecodingParams
33
+ from vllm.sampling_params import StructuredOutputsParams
35
34
 
36
35
  from mostlyai.engine._language.common import is_bf16_supported
37
36
  from mostlyai.engine._language.engine.base import EngineMetrics, LanguageEngine
@@ -80,8 +79,11 @@ class VLLMEngine(LanguageEngine):
80
79
 
81
80
  model_path = str(model_path)
82
81
  self._lora_request = LoRARequest("adapter", 1, model_path)
83
- config_max_model_len = _get_and_verify_max_len(
84
- base_config, tokenizer_config=None, max_model_len=None, disable_sliding_window=False, sliding_window=None
82
+ # Get max model length from config (different models use different attribute names)
83
+ config_max_model_len = getattr(
84
+ base_config,
85
+ "max_position_embeddings",
86
+ getattr(base_config, "n_positions", getattr(base_config, "max_sequence_length", 2048)),
85
87
  )
86
88
 
87
89
  self.llm = LLM(
@@ -136,18 +138,18 @@ class VLLMEngine(LanguageEngine):
136
138
 
137
139
  sampling_params = []
138
140
  for i in range(actual_batch_size):
139
- guided_decoding = None
141
+ structured_outputs = None
140
142
  if effective_schemas and i < len(effective_schemas):
141
- # Convert Pydantic model to JSON schema for guided decoding
143
+ # Convert Pydantic model to JSON schema for structured output
142
144
  schema_dict = effective_schemas[i].model_json_schema()
143
- guided_decoding = GuidedDecodingParams(json=schema_dict)
145
+ structured_outputs = StructuredOutputsParams(json=schema_dict)
144
146
 
145
147
  sampling_params.append(
146
148
  SamplingParams(
147
149
  max_tokens=self.max_new_tokens,
148
150
  temperature=sampling_temperature,
149
151
  top_p=sampling_top_p,
150
- guided_decoding=guided_decoding,
152
+ structured_outputs=structured_outputs,
151
153
  )
152
154
  )
153
155
  t_generate = time.time()
@@ -75,14 +75,23 @@ def create_schemas(
75
75
  numeric_fields = field_types.get(ModelEncodingType.language_numeric, [])
76
76
  datetime_fields = field_types.get(ModelEncodingType.language_datetime, [])
77
77
  cache = {}
78
+
79
+ def _normalize_seed_value(seed_value):
80
+ return None if pd.isna(seed_value) else seed_value
81
+
78
82
  for _, seed_row in seed_df.iterrows():
79
- cache_key = hash(tuple(sorted([(field_name, str(seed_value)) for field_name, seed_value in seed_row.items()])))
83
+ normalized_seed_items = [
84
+ (field_name, _normalize_seed_value(seed_value)) for field_name, seed_value in seed_row.items()
85
+ ]
86
+ cache_key = hash(
87
+ tuple(sorted([(field_name, str(seed_value)) for field_name, seed_value in normalized_seed_items]))
88
+ )
80
89
  if cache_key in cache:
81
90
  yield cache[cache_key]
82
91
  continue
83
92
  model_dict = {}
84
93
  if not seed_row.empty:
85
- model_dict |= {field_name: (Literal[seed_value], ...) for field_name, seed_value in seed_row.items()} # type: ignore[valid-type]
94
+ model_dict |= {field_name: (Literal[seed_value], ...) for field_name, seed_value in normalized_seed_items} # type: ignore[valid-type]
86
95
  for field_name in unseeded_fields:
87
96
  if field_name in categorical_fields:
88
97
  categories = stats["columns"][field_name]["categories"]
@@ -164,10 +164,11 @@ def _regressor_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int,
164
164
  def _flat_context_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int) -> list[int]:
165
165
  if isinstance(model_size, dict):
166
166
  return model_size[id]
167
- model_size_layers = dict(S=[8], M=[64], L=[128])
167
+ model_size_layers = dict(S=[2], M=[8], L=[32])
168
168
  layers = model_size_layers[model_size]
169
169
  coefficient = round(np.log(max(dim_input, np.e)))
170
170
  dims = [unit * coefficient for unit in layers]
171
+ _LOG.info(f"[ARGN] flat context heuristic: {dim_input=} -> {dims}")
171
172
  return dims
172
173
 
173
174
 
@@ -176,10 +177,11 @@ def _sequential_context_heuristic(
176
177
  ) -> list[int]:
177
178
  if isinstance(model_size, dict):
178
179
  return model_size[id]
179
- model_size_layers = dict(S=[8], M=[32], L=[64, 64])
180
+ model_size_layers = dict(S=[4], M=[16], L=[64, 64])
180
181
  layers = model_size_layers[model_size]
181
182
  coefficient = round(np.log(max(dim_input * seq_len_median, np.e)))
182
183
  dims = [unit * coefficient for unit in layers]
184
+ _LOG.info(f"[ARGN] sequential context heuristic: {dim_input=} x {seq_len_median=} -> {dims}")
183
185
  return dims
184
186
 
185
187
 
@@ -190,6 +192,7 @@ def _history_heuristic(id: str, model_size: ModelSizeOrUnits, dim_input: int, se
190
192
  layers = model_size_layers[model_size]
191
193
  coefficient = round(np.log(max(dim_input * seq_len_median, np.e)))
192
194
  dims = [unit * coefficient for unit in layers]
195
+ _LOG.info(f"[ARGN] history heuristic: {dim_input=} x {seq_len_median=} -> {dims}")
193
196
  return dims
194
197
 
195
198
 
@@ -197,7 +197,7 @@ def prepare_context_inputs(
197
197
  # Build flat context inputs (CTXFLT/*)
198
198
  ctxflt_inputs = {
199
199
  col: torch.unsqueeze(
200
- torch.as_tensor(ctx_encoded[col].to_numpy(), device=device).type(torch.int),
200
+ torch.as_tensor(ctx_encoded[col].to_numpy(copy=True), device=device).type(torch.int),
201
201
  dim=-1,
202
202
  )
203
203
  for col in ctx_encoded.columns
@@ -640,10 +640,9 @@ def decode_buffered_samples(
640
640
  keys=keys,
641
641
  key_name=tgt_context_key,
642
642
  )
643
- df_syn = df_syn.drop(
644
- columns=[c for c in df_syn.columns if c.startswith(POSITIONAL_COLUMN)],
645
- axis=1,
646
- ).reset_index(drop=True)
643
+ df_syn = df_syn.drop(columns=[c for c in df_syn.columns if c.startswith(POSITIONAL_COLUMN)]).reset_index(
644
+ drop=True
645
+ )
647
646
  else:
648
647
  data, seed_data = zip(*buffer.buffer)
649
648
  df_syn = pd.concat(data, axis=0).reset_index(drop=True)
@@ -820,6 +819,7 @@ def generate(
820
819
 
821
820
  if not enable_flexible_generation:
822
821
  check_column_order(gen_column_order, trn_column_order)
822
+
823
823
  _LOG.info(f"{rare_category_replacement_method=}")
824
824
  rare_token_fixed_probs = fix_rare_token_probs(tgt_stats, rare_category_replacement_method)
825
825
  imputation_fixed_probs = _fix_imputation_probs(tgt_stats, imputation)
@@ -1223,7 +1223,9 @@ def generate(
1223
1223
  # Use context inputs prepared earlier
1224
1224
  x = ctx_inputs
1225
1225
  fixed_values = {
1226
- col: torch.as_tensor(seed_batch_encoded[col].to_numpy(), device=model.device).type(torch.int)
1226
+ col: torch.as_tensor(seed_batch_encoded[col].to_numpy(copy=True), device=model.device).type(
1227
+ torch.int
1228
+ )
1227
1229
  for col in seed_batch_encoded.columns
1228
1230
  if col in tgt_sub_columns
1229
1231
  }
@@ -245,6 +245,7 @@ def _generate_marginal_probs(
245
245
  tgt_stats: dict,
246
246
  seed_columns: list[str],
247
247
  device: torch.device,
248
+ n_samples: int,
248
249
  ctx_data: pd.DataFrame | None = None,
249
250
  ctx_stats: dict | None = None,
250
251
  fixed_probs: dict | None = None,
@@ -259,6 +260,7 @@ def _generate_marginal_probs(
259
260
  tgt_stats: Target statistics
260
261
  seed_columns: Seed column names in original format, in correct order
261
262
  device: Device for computation
263
+ n_samples: Number of samples to generate probabilities for
262
264
  ctx_data: Optional context data
263
265
  ctx_stats: Optional context statistics (required if ctx_data provided)
264
266
  fixed_probs: Optional fixed probabilities for rare token handling
@@ -266,7 +268,6 @@ def _generate_marginal_probs(
266
268
  Returns:
267
269
  DataFrame of shape (n_samples, cardinality) with probabilities and column names
268
270
  """
269
- n_samples = len(seed_encoded)
270
271
  target_stats = tgt_stats["columns"][target_column]
271
272
 
272
273
  # Build fixed_values dict from seed_encoded
@@ -362,15 +363,14 @@ def predict_proba(
362
363
  )
363
364
  )
364
365
 
365
- # Get seed column names (needed for column order check and _generate_marginal_probs)
366
366
  seed_columns = list(seed_data.columns)
367
367
 
368
- # Check column order when flexible generation is disabled
369
368
  if not enable_flexible_generation:
370
369
  seed_columns_argn = get_argn_column_names(tgt_stats["columns"], seed_columns)
371
370
  target_columns_argn = get_argn_column_names(tgt_stats["columns"], target_columns)
372
- gen_column_order = seed_columns_argn + target_columns_argn
373
- check_column_order(gen_column_order, all_columns)
371
+ columns_to_check = seed_columns_argn + target_columns_argn
372
+ expected_order = [col for col in all_columns if col in columns_to_check]
373
+ check_column_order(columns_to_check, expected_order)
374
374
 
375
375
  # Encode seed data (features to condition on) - common for both single and multi-target
376
376
  # seed_data should NOT include any target columns
@@ -407,6 +407,7 @@ def predict_proba(
407
407
  tgt_stats=tgt_stats,
408
408
  seed_columns=seed_columns,
409
409
  device=device,
410
+ n_samples=n_samples,
410
411
  ctx_data=ctx_data,
411
412
  ctx_stats=ctx_stats,
412
413
  fixed_probs=fixed_probs,
@@ -451,10 +452,10 @@ def predict_proba(
451
452
  # Build DataFrames for each combo with actual values, then concatenate
452
453
  combo_dfs = []
453
454
  for combo_idx, prev_combo in enumerate(prev_combos):
454
- # Copy extended_seed for this combo
455
- df = extended_seed.copy()
455
+ # Build data dict starting with columns from extended_seed
456
+ data = {col: extended_seed[col].values for col in extended_seed.columns}
456
457
 
457
- # Add previous target columns with actual values (no dummy values)
458
+ # Add previous target columns with actual values
458
459
  for i in range(target_idx):
459
460
  prev_target_col = target_columns[i]
460
461
  encoded_val = prev_combo[i]
@@ -466,8 +467,10 @@ def predict_proba(
466
467
  argn_column=prev_target_stats[ARGN_COLUMN],
467
468
  argn_sub_column=sub_col_key,
468
469
  )
469
- df[full_sub_col_name] = encoded_val
470
+ data[full_sub_col_name] = encoded_val
470
471
 
472
+ # Create DataFrame with explicit row count
473
+ df = pd.DataFrame(data, index=range(n_samples))
471
474
  combo_dfs.append(df)
472
475
 
473
476
  # Concatenate all combo DataFrames into single batch
@@ -490,6 +493,7 @@ def predict_proba(
490
493
  tgt_stats=tgt_stats,
491
494
  seed_columns=extended_seed_columns,
492
495
  device=device,
496
+ n_samples=n_samples * num_prev_combos,
493
497
  ctx_data=batched_ctx_data,
494
498
  ctx_stats=ctx_stats,
495
499
  fixed_probs=fixed_probs,
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "mostlyai-engine"
3
- version = "2.3.3"
3
+ version = "2.5.0"
4
4
  description = "Synthetic Data Engine"
5
5
  authors = [{ name = "MOSTLY AI", email = "dev@mostly.ai" }]
6
- requires-python = ">=3.10"
6
+ requires-python = ">=3.11,<3.14"
7
7
  readme = "README.md"
8
8
  license = "Apache-2.0"
9
9
  classifiers = [
@@ -14,7 +14,6 @@ classifiers = [
14
14
  "Intended Audience :: Financial and Insurance Industry",
15
15
  "Intended Audience :: Healthcare Industry",
16
16
  "Intended Audience :: Telecommunications Industry",
17
- "Programming Language :: Python :: 3.10",
18
17
  "Programming Language :: Python :: 3.11",
19
18
  "Programming Language :: Python :: 3.12",
20
19
  "Programming Language :: Python :: 3.13",
@@ -25,7 +24,7 @@ classifiers = [
25
24
  ]
26
25
 
27
26
  dependencies = [
28
- "setuptools>=77.0.3", # similar to vllm 0.10.1.1
27
+ "setuptools>=77.0.3",
29
28
  "numpy>=2.0.0",
30
29
  "pandas>=2.2.0",
31
30
  "pyarrow>=16.0.0",
@@ -33,24 +32,24 @@ dependencies = [
33
32
  "scikit-learn>=1.4.0",
34
33
  "psutil>=5.9.5,<6", # upgrade when colab psutil is updated
35
34
  "tokenizers>=0.21.0",
36
- "transformers>=4.55.0", # updated (removed ,<4.54.0), similar to vllm 0.10.1.1
35
+ "transformers>=4.55.0,<5", # keep <5 for vllm==0.12 compatibility
37
36
  "datasets>=3.0.0",
38
37
  "accelerate>=1.5.0",
39
38
  "peft>=0.12.0",
40
39
  "huggingface-hub[hf-xet]>=0.30.2",
41
40
  "opacus>=1.5.4",
42
- "xgrammar>=0.1.21", # for vllm 0.10.1.1 compatibility (=0.1.21)
41
+ "xgrammar>=0.1.21",
43
42
  "json-repair>=0.47.0",
44
- "torch>=2.8.0,<2.9.0", # for vllm 0.10.2 compatibility
45
- "torchaudio>=2.8.0,<2.9.0", # for vllm 0.10.2 compatibility
46
- "torchvision>=0.23.0,<0.24.0" # for vllm 0.10.2 compatibility
43
+ "torch>=2.9.0,<2.10.0",
44
+ "torchaudio>=2.9.0,<2.10.0",
45
+ "torchvision>=0.24.0,<0.25.0"
47
46
  ]
48
47
 
49
48
  [project.optional-dependencies]
50
49
  gpu = [
51
50
  "bitsandbytes==0.42.0; sys_platform == 'darwin'",
52
51
  "bitsandbytes>=0.45.5; sys_platform == 'linux'",
53
- "vllm==0.10.2; sys_platform == 'linux' or sys_platform == 'darwin'",
52
+ "vllm==0.12.0; sys_platform == 'linux' or sys_platform == 'darwin'",
54
53
  ]
55
54
 
56
55
  [dependency-groups]
@@ -95,8 +94,9 @@ requires = ["hatchling", "hatch-vcs"]
95
94
  build-backend = "hatchling.build"
96
95
 
97
96
  [tool.ruff]
98
- target-version = "py310"
97
+ target-version = "py311"
99
98
  line-length = 120
99
+ extend-exclude = ["*.ipynb"]
100
100
  [tool.ruff.format]
101
101
  exclude = ["examples/*.ipynb"]
102
102
  [tool.ruff.lint]
File without changes