be_great: Fine-Tuning a Language Model to Write Rows of a Table
A novel approach for synthesizing tabular data using pretrained large language models
At a glance
- What is it?
- GReaT turns a pandas DataFrame into training text for a pretrained Transformer, then samples new rows token by token. The API is short, the constraint handling is real, and the cost model is a language model fine-tune, not a GAN.
- Who is it for?
- Adopt be_great when you already have a Hugging Face model you can fine-tune and you need synthetic rows, missing-value imputation, or conditional samples that satisfy numeric and categorical predicates. Do not adopt it when you need a fitted generator trained in seconds on CPU, or when you cannot afford a fine-tune per dataset.
- 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 30 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 September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem GReaT is aimed at
Synthetic tabular data has a long history of generative models that treat a table as a matrix of numbers. Categorical columns get one-hot encoded, dates get decomposed, and the model learns a joint distribution over the encoded space. GReaT takes a different route. It converts each row into a string of text and hands the whole thing to a pretrained Transformer language model as a fine-tuning corpus. The README describes the framework as using advanced pretrained Transformer language models to produce high-quality synthetic tabular data, and cites a publication at arxiv.org/pdf/2210.06280 for the method.
The intended user is someone who has a DataFrame and wants more rows like it, without designing an encoder for every column type. The README also points to a Kaggle notebook where the framework was used to generate synthetic crab age data, so the practical framing is competition and research work rather than production feature stores. There is no homepage listed for the repository, and the package is published on PyPI as be-great.
Rows become text, and text becomes rows again
The mechanism visible in the README is straightforward. You construct a GReaT object with a model identifier, call fit() on a DataFrame, and the library fine-tunes that language model on a serialized form of your rows. Calling sample(n_samples=100) then generates new rows in the same serialized format.
Because the model sees text, column names are part of the training signal. That is the interesting design bet: the model can learn that a column called age behaves differently from a column called city without anyone writing a type schema. It is also the main fragility. The README does not document how column names are escaped, what happens with duplicate names, or how a column name containing the delimiter would be handled. If your headers are machine-generated strings with punctuation, that is worth checking before you fit anything.
The library wraps the Hugging Face transformers Trainer, which the README acknowledges when it notes that save() writes a model.pt and a config.json in addition to the checkpoints the Trainer stores. So the training loop, mixed precision, and dataloader worker settings are all coming from that stack, exposed through GReaT constructor arguments.
Installing and running the quickstart
Installation is one command, and the README states it requires Python 3.9 or later:
pip install be-great
The quickstart fits on the California Housing dataset fetched through scikit-learn. The constructor takes the model identifier, batch size, epoch count, fp16 flag, and dataloader_num_workers:
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)
Persistence uses save() and load_from_dir(), and the README says both go through fsspec, so an s3://my_bucket path works in the same call as a local directory. That is a small feature but it matters operationally: a fine-tuned checkpoint is a large artifact, and being able to write it straight to object storage without a separate upload step removes a failure point.
The README also documents two tuning parameters for difficult data. float_precision=3 rounds numeric values to three decimals before serialization, which the README explains as helping the model focus on patterns rather than memorizing exact values, and as being particularly useful for small datasets where overfitting is a concern. The second is guided_sampling=True on sample(), which switches to feature-by-feature generation and is described as potentially slower than the default but more reliable for datasets with many features or complex relationships. random_feature_order=True is offered alongside it to avoid ordering bias, and temperature controls diversity.
Constraints are enforced during generation, not filtered after
The conditional sampling interface is the most concrete differentiator in the README. You pass a conditions dictionary to sample(), and the README states that constraints are enforced during token generation, so every output row is valid with zero waste. Numeric columns accept >=, <=, >, <, ==, and !=. Categorical columns accept == and !=, with values quoted in single quotes, as in "== 'Female'". Multiple conditions combine in one call, and guided sampling turns on automatically when conditions are present.
This is a meaningful difference from the common pattern of generating a large batch and discarding rows that fail a predicate. If the constraint is selective, rejection sampling wastes most of the generation budget. Enforcing at token level means the cost of a constrained sample is closer to the cost of an unconstrained one, though the README does not quantify the overhead, and guided sampling being auto-enabled means you are paying the slower path whenever conditions are set.
The example in the README uses the UCI Adult dataset with float_precision=0 and conditions on age, hours-per-week, and sex. Note that the value quoting rule is strict: an unquoted categorical value is not documented as valid, so a typo there is a likely first failure.
Imputation and schema-only mock data
Two interfaces sit outside the generate-more-rows use case. The first is impute(). Given a trained model and a DataFrame where missing entries are set to NaN, model.impute(test_data, max_length=200) fills them in. The README's example drops values at random across every column before calling it, which means the method is documented for arbitrary missingness patterns rather than a fixed column. That is a genuinely useful capability if you have a dataset with scattered gaps, and it reuses the same fitted model rather than requiring a separate imputer.
The second is mock data generation from a declarative schema, which requires no fit() and no training data. The README frames it for privacy-safe dummy data, test fixtures, dev environments, and schema prototyping, and recommends pairing it with a tabular-pretrained model. The schema dictionary carries per-column type information, including a range tuple, an integer flag, a distribution name with mean and standard deviation, and a null_prob. The README's excerpt is truncated mid-example, so the full set of supported schema keys is not visible in the material available here. Treat the schema format as documented by example rather than by specification.
Where this approach stops being the right tool
The cost model is the limitation that matters most. GReaT fine-tunes a Transformer. The README's own examples use epochs values of 5, 50, and 100, and the guidance for small datasets is to raise epochs to 100 with a smaller batch size. That is a real training run per dataset, not a fit in seconds. If your workflow regenerates synthetic data every time the source table refreshes, you are either re-fitting on every refresh or storing a checkpoint per version and accepting staleness. Neither is free.
The second limitation is that the approach inherits the language model's tokenizer. Numeric columns are serialized as text and generated as text, which is why float_precision exists as a mitigation. Rounding to three decimals changes the distribution of your numeric columns; it is a deliberate trade of fidelity for learnability, and the README presents it that way rather than as a lossless option.
The third is documentation depth. The README does not state what happens when a condition references a column name that does not exist, how the library handles high-cardinality categorical columns, or what the default sampling path does when guided sampling is off. The publication is cited for method details, but the operational edge cases are not enumerated in the repository material.
Finally, this is version 0.0.14. The release history shows 0.0.9 in May 2025, 0.0.13 in February 2026, and 0.0.14 in May 2026, so the project is active, but the leading zeros are honest about API stability.
The alternative you are probably weighing
The obvious comparison is SDV, the Synthetic Data Vault, which is the default choice for tabular synthesis in Python. The difference in approach is architectural. SDV fits statistical and neural models over an explicitly typed representation of your table: you declare which columns are numerical, categorical, or datetime, and the model learns a distribution over that encoded space. GReaT skips the type declaration and lets a language model learn the structure from serialized text.
That split has practical consequences. SDV's typed pipeline gives you deterministic control over how a date column is treated and makes it easy to reason about what the model can and cannot represent. GReaT's text pipeline gives you the constraint-enforcement mechanism and the imputation interface without a separate encoder, and it benefits from whatever the base language model already knows about tabular structure. If your columns are mostly numeric with a few low-cardinality categoricals, SDV's explicit typing is easier to debug. If you have many heterogeneous columns and want conditional generation with predicates, GReaT's token-level constraint enforcement is the feature SDV does not match in the same way.
A second alternative worth naming is simply training a gradient-boosted model to predict one column from the others and sampling from it column by column. That is cheaper than either and produces plausible rows, but it captures only the conditional distributions you explicitly model, not a joint one. It is the right tool when you need a specific column populated, and the wrong tool when you need the joint distribution preserved.
Licence, maintenance, and what to check first
The repository is MIT licensed. That is permissive: you can use it commercially, modify it, and redistribute it, provided the copyright notice and permission notice are retained. It is worth noting separately that the licence covers the be_great code, not the pretrained model you point llm= at. The README's examples reference tabularisai/Qwen3-0.3B-distil on Hugging Face and distilgpt2. Each of those has its own licence and its own terms, and fine-tuning a model can produce a derivative artifact whose redistribution terms follow the base model's licence. Check the model card for whichever checkpoint you choose; the MIT licence on this package tells you nothing about it. This is not legal advice.
On maintenance cost: the package pins nothing visible in the README, but it depends on transformers, and the transformers API moves. A library that wraps the Trainer and relies on fp16 and dataloader_num_workers arguments will feel version drift in that dependency before it feels it anywhere else. Budget for reading release notes on transformers upgrades, not just on be-great upgrades.
Upgrade cost between be-great versions is the open question. The jump from 0.0.9 to 0.0.13 spans roughly nine months and the README documents conditional sampling and mock data generation as current features; whether those existed in 0.0.9 is not stated. Because saved checkpoints include a config.json, a checkpoint written by an older version may or may not load under a newer one. The README does not address cross-version checkpoint compatibility, so if you archive checkpoints, test load_from_dir() against the version that wrote them before you need them.
Editorial conclusion
Adopt be_great when you already have a Hugging Face model you can fine-tune and you need synthetic rows, missing-value imputation, or conditional samples that satisfy numeric and categorical predicates. Do not adopt it when you need a fitted generator trained in seconds on CPU, or when you cannot afford a fine-tune per dataset. Before committing, verify three things on your own data: whether your column names survive the row serialization intact, how long one fit() takes on your target GPU, and whether the conditions dictionary accepts your categorical values with the quoting rules the README specifies.
Community notes