Library / SDK
tabularis-ai/be_great avatar
tabularis-ai/be_great

be_great generates tables with a 0.3B language model and a list of knobs

A novel approach for synthesizing tabular data using pretrained large language models

371 stars59 forksPythonMIT

At a glance

What is it?
A synthetic tabular data library that fine-tunes a pretrained transformer on your rows, then either samples freely or enforces conditions such as age >= 30 during token generation, with a schema-only path that needs no training data at all and three of whose documented examples stop mid-statement.
Who is it for?
Adopt be_great if you need rows that resemble a real distribution for testing, schema work or privacy-safe fixtures, and if you can afford to fine-tune rather than only prompt. Do not adopt it for data you cannot leak to a model download, and do not trust the conditional examples as copy-paste, because the visible snippets are cut off mid-statement.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 56 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Rows become text, and a 0.3B model learns your columns

be_great, the GReaT framework, generates synthetic tabular data by fine-tuning a pretrained transformer language model on your data rather than by fitting a dedicated tabular model.

The quickstart makes that concrete, using the California Housing dataset:

python
from be_great import GReaT
from sklearn.datasets import fetch_california_housing

data = fetch_california_housing(as_frame=True).frame

model = GReaT(llm='tabularisai/Qwen3-0.3B-distil', batch_size=32,  epochs=5,
              fp16=True, dataloader_num_workers=4)
model.fit(data)
synthetic_data = model.sample(n_samples=100)

The default model in the documentation is a 0.3B-parameter tabular-pretrained Qwen3 distillation, which is a deliberate choice about scale. A 0.3B model fine-tunes on a laptop-class GPU, which is what makes the library usable outside a research cluster. The conditional generation example uses `distilgpt2` instead, so the base model is a parameter you choose rather than a fixed assumption.

The method is described in a publication on arXiv, 2210.06280, and the project reports adoption on Google's Kaggle platform, where the framework has been used to generate synthetic datasets across multiple competitions.

Four knobs decide whether a small dataset works

Small data and wide tables are the cases the library argues about explicitly, and the parameters are given as a set.

`float_precision` limits the decimal places kept in numerical values, so a column of prices is not memorised digit by digit. `batch_size` is lowered for small datasets, `epochs` is raised, and `fp16` enables half-precision training for speed and lower memory use. Together those four are the recommended configuration when you have few rows or many features.

The fifth parameter, `guided_sampling`, changes how generation works rather than how training works. It enables a feature-by-feature generation approach, which is potentially slower than the default sampling method but produces more reliable results on datasets with many features or complex relationships.

The reasoning behind it is worth stating, because it explains a lot of what this library is for. A generative model asked for a hundred rows of a wide table has to invent plausible joint values across every column at once, and that is where wide, sparsely observed data fails. Generating column by column trades speed for a much tighter constraint on what the model has to get right simultaneously.

Round the decimals and the model has less to memorise, which is also why `float_precision` is the recommended lever against overfitting on small tables.

Conditions are enforced while tokens are generated, so no row is discarded

Constrained sampling is the feature that separates this from post-filtering, and the difference is stated in the mechanism rather than the marketing.

Conditions are enforced during token generation, so every output row satisfies them and nothing is thrown away. A conventional implementation would sample freely and then drop the rows that fail, which on a selective constraint like age above 30 can mean throwing away most of what you generated.

The vocabulary is explicit. Numeric columns accept `>=`, `<=`, `>`, `<`, `==` and `!=`. Categorical columns accept `==` and `!=`, with values quoted using single quotes, as in `== 'Female'`. Several conditions combine in a single call, and guided sampling switches on automatically whenever conditions are present.

The documented example uses the UCI Adult dataset, dropping the rows that carry a `?` placeholder before training, with `float_precision=0` because the columns are effectively categorical:

python
model = GReaT(llm='distilgpt2', epochs=50, batch_size=32, float_precision=0)
model.fit(df)

Automatic guided sampling on the constrained path is a sensible default. A conditional request is exactly the case where getting one column wrong wastes the whole row, so the slower mode is the right trade.

Imputation fills arbitrary combinations of missing values

A second interface reuses a trained model to fill gaps rather than to invent new rows, which is a different problem with the same machinery.

`impute` takes a DataFrame whose missing values are set to NaN and fills them, accepting arbitrary combinations of missing entries rather than requiring a whole row or a whole column to be absent. The parameter that bounds the work is `max_length`, passed as 200 in the documented example:

python
imputed_data = model.impute(test_data, max_length=200)

The requirement is a model already trained on the distribution you want to impute, plus a frame of samples from that same distribution. The example manufactures the missingness first, replacing each cell with NaN with probability one half, which is a quick way to produce something to test against.

This is worth separating from generation in your own head. Generated rows are new records with no counterpart in reality, while imputed rows are partially real records with holes filled in. If you use both, only one of the two needs the privacy argument you would apply to synthetic data.

Mock schemas skip fit() entirely for fixtures and dev data

The newest interface in the visible documentation goes the other way and removes training altogether.

Rows are generated from a declarative schema: no `fit()` call and no training data, which the documentation positions for privacy-safe dummy data, test fixtures, dev environments and schema prototyping. The suggested companion is the same tabular-pretrained 0.3B model, since the interface works best with a base that already understands tabular text.

A column is declared with a type and its parameters. A `num` column takes a `range`, with optional `integer`, `precision`, and `dist="normal"` alongside `mean` and `std`. A `cat` column takes `values` with optional `weights`, given as a list or a dict, so a class imbalance like 0.76 and 0.24 is expressed directly.

Two features make it more than a random data generator. `null_prob`, between zero and one, injects realistic missing values per column after generation, which is what makes the output usable as a test fixture rather than a demo table. And `conditions=` reuses the same operator vocabulary as `sample()`, so schema constraints and trained-model constraints are written the same way.

The tree also holds example scripts for the trained path and the schema path, including a conditional Adult example and a random preconditioning example, so both are demonstrable without writing the schema yourself.

A checkpoint is a directory, and fsspec carries it to S3

Persistence is handled in two ways, and the second one is the reason to look.

Beyond the checkpoints the HuggingFace transformers Trainer stores during training, the library offers explicit save and load methods. Saving writes a `model.pt` and a `config.json` into a directory, and loading reads the directory back:

python
model.save("my_directory")  # saves a "model.pt" and a "config.json" file
model = GReaT.load_from_dir("my_directory")  # loads the model again

The same two calls accept remote paths, because remote filesystems are supported through fsspec:

python
model.save("s3://my_bucket")
model = GReaT.load_from_dir("s3://my_bucket")

That is a small feature with a large practical effect. A fine-tuned generator is an artefact you want to reuse across environments, and writing it straight to object storage removes the step where somebody uploads a directory by hand.

Installation is `pip install be-great` with Python 3.9 or newer. The package builds on torch, transformers and accelerate, with datasets, pandas, numpy, scikit-learn and scipy, plus a chugchug dependency, and a `lora` extra pulling in peft, which the visible documentation never explains.

Three visible examples are cut off, and the two dependency files differ

Two documentation problems are worth knowing before you rely on the snippets.

The first is truncation. Of the code blocks visible in this copy of the README, three stop mid-statement: the guided sampling call ends at `guided_sampling=T`, the conditional example's conditions dictionary ends on a bare quote and comma, and the mock schema example ends at the comment marker `# O`. Each of those leaves a file that will not parse, so the three examples need to be completed from the documentation site rather than copied.

The second is a divergence between the two dependency files. The packaging metadata lists scipy among the required dependencies; requirements.txt does not. requirements.txt lists fsspec; the packaging metadata does not. Both lists name datasets, numpy, pandas, scikit-learn, torch, tqdm, chugchug, transformers and accelerate, so the disagreement is narrow, but it decides whether a fresh environment gets fsspec, which the saving and loading feature depends on.

Maintenance facts: the package is at version 0.0.14 under MIT, the last push landed on 2026-08-16, and the release history is 0.0.14 in May 2026, 0.0.13 in February 2026 and 0.0.9 in May 2025. The 0.0.x numbering is the honest signal here, since an interface with condition syntax and a schema format added in the last year can still move.

Editorial conclusion

Adopt be_great if you need rows that resemble a real distribution for testing, schema work or privacy-safe fixtures, and if you can afford to fine-tune rather than only prompt. Do not adopt it for data you cannot leak to a model download, and do not trust the conditional examples as copy-paste, because the visible snippets are cut off mid-statement. Check your own requirements file against pyproject before installing, since scipy and fsspec are listed in one and not the other.

Frequently asked questions

How do I install be_great?

Run `pip install be-great`, which requires Python 3.9 or newer. The package builds on torch, transformers and accelerate, and the main entry point is imported as `from be_great import GReaT`.

How do I generate synthetic rows that satisfy a condition?

Pass a conditions mapping to `sample()`, for example `{"age": ">= 40", "hours-per-week": "<= 40"}`. Numeric columns accept >=, <=, >, <, == and !=, categorical columns accept == and != with single-quoted values, several conditions combine in one call, and constraints are enforced during token generation.

Can be_great generate data without a training set?

Yes. The mock dataset interface generates rows from a declarative schema with no fit() call, using per-column types, ranges, distributions, categorical weights and a null probability. The documentation positions it for privacy-safe dummy data, test fixtures, dev environments and schema prototyping.

How do I save and reload a GReaT model?

Call `model.save("my_directory")`, which writes a model.pt and a config.json, then `GReaT.load_from_dir("my_directory")` to read it back. Remote filesystems are supported through fsspec, so the same calls work with a path such as `s3://my_bucket`.

How does be_great fill in missing values?

With `model.impute(test_data, max_length=200)` on a DataFrame whose missing entries are NaN, using a model already trained on the distribution you want to fill. Arbitrary combinations of missing cells are handled, not only whole rows or columns.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. tabularis-ai/be_great on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tabularis-ai-be-great.svg)](https://hysenlabs.com/projects/tabularis-ai-be-great)