# The package metadata excludes Python 3.13 while the page and the classifiers both claim it, and there is deliberately no TPU extra

> A TensorFlow 2 speech recognition toolkit covering transducer and CTC architectures with TFLite export. The packaging decisions in the manifest are better argued than anything in the page, which is now an index of sub-documents.

**TensorSpeech/TensorFlowASR** — :zap: TensorFlowASR: Almost State-of-the-art Automatic Speech Recognition in Tensorflow 2. Supported languages that can use characters or subwords

- Repository: https://github.com/TensorSpeech/TensorFlowASR
- Website: https://huylenguyen.com/asr
- Stars: 1,015 · Forks: 237
- Language: Python
- License: Apache-2.0
- Published: 2026-09-16 · Updated: 2026-09-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/tensorspeech-tensorflowasr

## The package refuses Python 3.13 and the documentation says it supports it

The interpreter requirement is a single line in the project metadata, and it is a range with an upper bound:

```toml
requires-python = ">=3.12,<3.13"
```

The page says the project requires Python 3.12 or 3.13. The classifier list in the same file names both 3.12 and 3.13.

So the two halves of the same file disagree, and the resolver will follow the range rather than the classifier. A 3.13 environment is refused at install time with a message about the Python version, which is a confusing way to learn about a project that claims to support your interpreter.

This is a one-character fix in the metadata, and the only reason to call it out is that it is the first thing anyone on a current Python hits.

The rest of the classifier list is narrower than the documentation in the same way. The operating system classifier names Linux and nothing else, while the page presents CPU and Apple Silicon as the supported paths and the manifest contains a platform marker for a Metal build that only publishes wheels for one macOS architecture. So the metadata is a Linux-only, Python-3.12-only package in a project that documents macOS as a first-class target.

## There is deliberately no TPU extra, and the script has to be re-run

The most interesting thing in this repository is a comment explaining why an extra does not exist.

Cloud TPU support is installed by a script rather than declared as a dependency extra, and the reasoning is specific. The TPU distribution of TensorFlow ships its own TensorFlow, and TensorFlow is already a base dependency. An extra adding it would therefore install both distributions at the same version, and whichever landed last would be the one in place.

So the flow is uninstall the stock build first, then install the TPU one:

```bash
uv sync && ./scripts/install_tpu.sh
```

And then the instruction that matters: re-run the script after any later sync, because a sync puts the stock TensorFlow back. The comment in the manifest restates it with a resolution example, two TensorFlow distributions both landing at the same version, either order accepted by the resolver.

That is a correct diagnosis and a working answer, and it is also a footgun that the documentation admits to rather than hides. Anything in your workflow that runs sync on a schedule, a container rebuild, or a dependency bump will silently revert a TPU install.

The container image demonstrates the hazard by example: it runs the sync step twice, once without the project and once with it, so the same operation that a TPU user must guard against is simply part of the normal build.

## The CUDA library path is written into a bash rc file

One line in the container build sets the library search path for CUDA:

```dockerfile
RUN echo "export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" >> /root/.bashrc
```

It is appended to a shell startup file rather than set as an environment variable, and it is written to a specific user's home directory.

Both halves have consequences. An environment variable would apply to every process the container starts, including the command in the entrypoint. A line in a startup file applies only when a shell sources it, so an entrypoint that execs a binary directly, or a process started without a login shell, does not get the path. And writing to a root-owned file assumes the container runs as root, which the rest of the file agrees with but does not enforce.

The expansion itself is careful. It appends to any existing value rather than overwriting it, which is the correct pattern and is more often got wrong.

For an image whose whole purpose is GPU training, this is the kind of line that works in an interactive shell and fails in a scheduled job, and it is exactly the kind of thing that only shows up after the environment is automated.

## The image starts from a TensorFlow image and then installs TensorFlow again

The base image is the GPU variant of TensorFlow at a pinned patch version, which already contains TensorFlow installed in the interpreter the image provides.

Then the build runs the sync command twice, once with the project excluded so the dependency layer stays cached across source changes, and once with the project, both with the CUDA extra and both frozen against the committed lock file.

So there are two copies of the same TensorFlow version, one from the base image and one installed by the resolver into a directory the build deliberately points at the system prefix rather than a nested environment:

```dockerfile
ENV UV_PROJECT_ENVIRONMENT=/usr/local
```

The reasoning for that choice is in a comment: installing into the image's existing interpreter rather than a nested environment. That is the right call when the base image already has an interpreter and a working TensorFlow, and it is what makes the second TensorFlow the one that wins.

The rest of the file is more conventional. A package manager is copied in from its own official image at a pinned version, byte compilation is enabled, and linking is set to copy rather than hardlink, which is the setting you need when the cache and the target live on different filesystems.

The apt layer is where the file gets untidy: a C library for PostgreSQL that nothing in the documentation suggests is used, a terminal multiplexer in a server image, and a cache clean that removes the package archives without removing the package index the install step created.

## The compose file publishes a debugging port nobody documents

The single-service compose file is short and has several things in it that the documentation never mentions.

```yaml
    ports:
      - 6006:6006
```

Port 6006 is the conventional port for a training dashboard. Nothing on the page or in the referenced tutorials starts one, so the mapping is inherited from a workflow rather than from a documented service.

Three more lines are worth reading together. Host IPC sharing is enabled, which is what a shared-memory or pinned-memory data loader needs and is a broad grant to get for it. The NVIDIA runtime key is used, which is the older mechanism superseded by device reservations in the current compose specification. And the file still carries a top-level version key, which recent versions of the compose tool warn about and ignore.

The mount is the one that looks alarming and turns out to be deliberate:

```yaml
    volumes:
      - ./:/app
```

The whole repository is mounted over the application directory, so the copy of the package installed during the build is shadowed at run time by the host's source tree. That works because of the environment prefix set earlier: the installed dependencies live outside the mounted directory, and only the source is shadowed. Edit a file on the host and the container sees it, with no rebuild.

## The container declares no command of its own

The container build ends after the library path line. There is no command and no entrypoint instruction anywhere in it.

The compose service declares no command either. What the page tells you to run is a Python interface through the package manager's run wrapper, for example the help flag for the command line tool, or the test runner.

So the image inherits whatever the base image does by default, and a developer starting the stack gets a container that mounts their repository, shares the host IPC namespace, claims the NVIDIA runtime and publishes a dashboard port, and then runs whatever TensorFlow's own image does when nothing is specified. If that is an interactive shell, the setup works well. If it is a Python process that exits, the stack looks broken.

This is the gap between a development environment and a service, and the page describes the development environment. The same page does describe a proper application interface, which transcribes one stream at a time from either a checkpoint or an exported TFLite model, in one pass or streaming chunk by chunk, with all sessions on one model sharing a single engine that loads the model once and decodes many sessions per call, for example one session per websocket connection. That is a server design, and it is described as a library you call rather than something the container starts.

## Two publications point at one examples directory

The supported-model list has six entries and six reference papers, and each is supposed to come with a path to its code.

Five of them do. The RNN transducer, the context network, Deep Speech 2 and Jasper each have their own directory under a transducer or connectionist classification tree.

The sixth is the odd one. The streaming conformer and the ordinary conformer are two different publications with two different reference links, and both of them point at the same examples directory.

So a reader who follows the streaming conformer entry lands in the conformer example and has to work out from the results whether the streaming variant is a configuration flag, a separate script, or simply not implemented yet.

The rest of the page has the same shape. Training, testing, feature extraction, decoders, inference, augmentations, the TFLite conversion walkthrough, pretrained results and the corpus sources are each one sentence long and each one link to a document elsewhere. That is the right structure for a project with this much surface area, and it means the front page is now an index rather than a tutorial.

Two details from those sentences are worth carrying. The decoder layer covers greedy and beam search for both families of model plus a named transducer beam search algorithm. And the Keras built-in training path uses an infinite dataset specifically so that there is never a final partial batch.

## Transducer training needs two packages cloned by hand

The training path depends on code this project does not ship, and the documentation says so in one sentence before the install instructions: for training and testing you clone the necessary packages from their original authors, naming a CTC decoder package and an RNN transducer loss package.

Neither is in the dependency list. Both are therefore outside the lock file, outside the extras and outside the version constraints, which means a training environment assembled from the page alone has two unpinned third-party builds in it.

The container handles it, and the way it handles it is worth reading. Both switches are build arguments:

```dockerfile
ARG install_rnnt_loss=true
ARG using_gpu=true
```

and the install runs only when the first is set, setting a CUDA home directory when the second is also set and otherwise printing that it is using the CPU. With both off, the build prints that it is using pure TensorFlow, which means the transducer loss is absent and the transducer models in the examples cannot be trained in that image.

So the same container that looks like a complete environment produces a tree that cannot train one of its two model families unless you leave the defaults in place.

The corpus table that follows is the other place worth reading before starting: the two English sources are listed with about two thousand nine hundred hours between them and the Vietnamese section opens with a fifteen-hour corpus. The page gives hours and sources but says nothing about which of them have preparation recipes in the examples.

## Conclusion

TensorFlowASR earns its place if you want an ASR stack that stays inside TensorFlow 2, needs TFLite export for on-device deployment, and wants both transducer and CTC training paths in one repository with published example results. The engineering judgment in its packaging is unusually good and the argument for it is written down in the manifest itself, which is rarer than it should be. Three things to check before you commit. Resolve the Python version, because the package metadata has an upper bound that excludes 3.13 while the documentation and the classifiers both advertise it, so a 3.13 environment will be refused by the resolver even though the project claims to support it. Decide how you will handle accelerators, because CUDA is a plain extra but Cloud TPU is not: it needs a mutating script run after every sync, and running sync again silently puts the stock TensorFlow back. And know that transducer training needs packages cloned from other authors rather than resolved, so a from-source training environment has a manual step the container image does for you. Who should not use it: anyone who needs a supported TPU install that survives an environment refresh without thinking about it. What this is not is a batteries-included transcription service. The container image declares no command of its own, the compose file publishes a port nothing in the documentation mentions, and the runtime path the page describes is a Python interface you call rather than a service you start.

## FAQ

### Which Python versions does TensorFlowASR support?

The page says 3.12 or 3.13 and the classifiers name both, but the package metadata requires a range from 3.12 up to but excluding 3.13. The resolver follows the range, so a 3.13 environment is refused at install.

### How do I install TensorFlowASR with NVIDIA GPU support?

Clone the repository and sync with the cuda extra, which installs the accelerator build of TensorFlow. A plain sync installs TensorFlow and its text library, which is what CPU and Apple Silicon need. Run commands inside the environment with the package manager's run wrapper.

### Why is there no TPU extra for TensorFlowASR?

Because the TPU distribution of TensorFlow ships its own TensorFlow and TensorFlow is already a base dependency, so an extra would install both at the same version and leave whichever landed last in place. A script uninstalls the stock build first and installs the TPU one, and has to be re-run after any later sync.

### Does the TensorFlowASR Docker image need a command to work?

The Dockerfile declares neither a command nor an entrypoint, and the compose service declares no command, so the image inherits its base image's behaviour. The page's own invocations are run-wrapper commands such as the test runner and the command line tool's help flag.

### What speech recognition architectures does TensorFlowASR implement?

Transducer models trained with a transducer loss, currently conformer, context network and a streaming transducer, and a connectionist model trained with a CTC loss, currently Deep Speech 2 and Jasper. All of them can be converted to TFLite, where a converted model becomes a direct function from an audio signal to text and tokens.

### Can TensorFlowASR do streaming and batch transcription from the same interface?

Yes. One interface transcribes a single stream from either a checkpoint or an exported TFLite model, either in one pass or streaming chunk by chunk, and all sessions on one model share a single engine that loads the model once and decodes many sessions per call, for example one session per websocket connection.

## Sources

- [License: Apache-2.0](https://github.com/TensorSpeech/TensorFlowASR/blob/main/LICENSE)
- [Project website](https://huylenguyen.com/asr)
- [README](https://github.com/TensorSpeech/TensorFlowASR/blob/main/README.md)
- [Releases](https://github.com/TensorSpeech/TensorFlowASR/releases)
- [TensorSpeech/TensorFlowASR on GitHub](https://github.com/TensorSpeech/TensorFlowASR)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tensorspeech-tensorflowasr
