Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/actions/lin-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ runs:
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
fi

- name: Build and run backend tests
if: inputs.variant == 'XRT-deb'
shell: bash
working-directory: ${{ github.workspace }}
run: |
for suite in iron_backend model_backend model_list_platform; do
cmake -S "src/test/$suite" -B "build-$suite" -DCMAKE_BUILD_TYPE=Release
cmake --build "build-$suite" --parallel 2
ctest --test-dir "build-$suite" --output-on-failure
done

# Everything cached lives under .ci-cache in the workspace: a container
# job's actions/cache resolves relative paths there, and an absolute
# ${{ github.workspace }} would name the host's path, not the container's.
Expand Down
14 changes: 14 additions & 0 deletions docs/docs/models/llama.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ parent: Models
flm run llama3.2:1b
```

With an IRON-enabled build, `llama3.2-iron:1b` uses the same Instruct model in
BF16, with the tokenizer and chat template from FastFlowLM's package above.
Accept Meta's model license and provide a Hugging Face token (`HF_TOKEN` or
`hf auth login`), then run:

```shell
flm pull llama3.2-iron:1b
flm run llama3.2-iron:1b --ctx-len 2048
```

This downloads into `models/llama3.2-1b-instruct`. If you previously pulled the
base checkpoint, pull again after updating FastFlowLM; the new directory keeps
the two checkpoints separate.

---

## 🧩 Model Card: [Llama-3.2-3B-Instruct](https://huggingface.co/meta-llama/Llama-3.2-3B-Instruct)
Expand Down
17 changes: 14 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ option(FLM_PORTABLE_BUILD "Build portable distribution with bundled runtime libr
# It also fixes the NPU generation the build targets -- see utils/npu_platform.hpp.
option(FLM_ENABLE_CORE
"Reach kernels through ryzenai-corelib instead of FastFlowLM's own flow" OFF)
option(FLM_ENABLE_IRON "Run complete IRON graphs through the IRON server" OFF)

if(FLM_ENABLE_IRON)
add_compile_definitions(FLM_ENABLE_IRON=1)
endif()

if(FLM_ENABLE_CORE)
# The corelib prefix lives outside this repository, so its headers have to
Expand Down Expand Up @@ -221,10 +226,16 @@ include("${CMAKE_SOURCE_DIR}/common/chat/chat_sources.cmake")
# ———————————————————————————————————————————————
file(GLOB SOURCES "src/*.cpp" "runner/*.cpp" "common/*.cpp" "common/*/*.cpp" "server/*.cpp" "pull/*.cpp" )
file(GLOB HEADERS "include/*.hpp" "runner/*.hpp" "common/*.hpp" "common/*/*.hpp" "server/*.hpp" "pull/*.hpp")
include(common/models/models_sources.cmake)
if(FLM_ENABLE_IRON)
list(APPEND SOURCES ${FLM_MODELS_IRON_SOURCES})
else()
list(FILTER SOURCES EXCLUDE REGEX ".*/common/iron/.*\\.cpp$")
endif()
list(FILTER SOURCES EXCLUDE REGEX ".*/common/core/.*\\.cpp$")

# Model sources live two levels deeper than the globs above reach. Only the core
# half exists as loose sources; it is built separately, below.
# Model sources live two levels deeper than the globs above reach. IRON links
# into flm. Core builds as a separate library below.

# Exclude files that depend on missing libraries for Linux
if(NOT WIN32)
Expand Down Expand Up @@ -705,7 +716,7 @@ if(WIN32)
target_link_libraries(flm PRIVATE
ntdll wsock32 ws2_32 Bcrypt
iphlpapi userenv psapi
crypt32 secur32 advapi32 normaliz wldap32
crypt32 secur32 advapi32 normaliz wldap32 shell32
)
else()
target_link_libraries(flm PRIVATE Threads::Threads)
Expand Down
10 changes: 10 additions & 0 deletions src/common/AutoModel/builtin_backends.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
#include "AutoModel/flm_backend.hpp"
#include "AutoModel/model_backend.hpp"

#if defined(FLM_ENABLE_IRON)
#include "models/llama3/iron/llama3_iron_backend.hpp"
#endif

#if defined(FLM_ENABLE_CORE)
#include "models/gemma4/core/dev04/gemma4_core_backend.hpp"
#include "models/llama/core/dev04/llama_core_backend.hpp"
Expand Down Expand Up @@ -63,6 +67,12 @@ void register_builtin_backends(BackendRegistry& registry) {
RegisterFlm<nanbeige_npu>(registry, "nanbeige");
RegisterFlm<phi4_npu>(registry, "phi4");

#if defined(FLM_ENABLE_IRON)
registry.register_backend("llama3", flm::backend::kIronBackendId,
flm::llama3::iron_factory(),
flm::llama3::iron_traits());
#endif

#if defined(FLM_ENABLE_CORE)
registry.register_backend("gemma4e", flm::backend::kCoreBackendId,
flm::gemma4::core_factory(),
Expand Down
106 changes: 106 additions & 0 deletions src/common/iron/causal_lm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
// SPDX-License-Identifier: MIT

#include "iron/causal_lm.hpp"

#include <cstring>
#include <memory>
#include <stdexcept>
#include <utility>

namespace flm::iron {
namespace {

std::uint32_t ValidateContextLength(std::uint32_t length) {
if (length == 0) {
throw std::out_of_range("IRON context length must be positive");
}
return length;
}

} // namespace

CausalLM::CausalLM(const std::string& model_path, std::string model,
std::uint32_t context_length)
: worker_(model_path, std::move(model), context_length),
max_length_(context_length) {}

buffer<bf16> CausalLM::forward(int id) {
tokens_.push_back(id);
return run();
}

buffer<bf16> CausalLM::prefill(std::vector<int>& ids, void*) {
tokens_.insert(tokens_.end(), ids.begin(), ids.end());
return run();
}

void CausalLM::set_context_length(int length) {
if (length < 0 || static_cast<std::size_t>(length) > tokens_.size()) {
throw std::out_of_range("Invalid IRON context length");
}
tokens_.resize(static_cast<std::size_t>(length));
}

void CausalLM::load_weights(Q4NX&) {
throw std::logic_error("The IRON server loads safetensors");
}

void CausalLM::update_max_length(std::uint32_t length) {
max_length_ = length;
}

void CausalLM::clear_context() {
tokens_.clear();
worker_.reset();
}

buffer<bf16> CausalLM::get_k_cache(int, int) { return {}; }

buffer<bf16> CausalLM::get_v_cache(int, int) { return {}; }

int CausalLM::get_current_context_length() {
return static_cast<int>(tokens_.size());
}

int CausalLM::checkpoint() { return static_cast<int>(tokens_.size()); }

int CausalLM::restore() { return 0; }

bool CausalLM::poisoned() const noexcept { return worker_.poisoned(); }

buffer<bf16> CausalLM::run() {
if (tokens_.empty() || tokens_.size() > max_length_) {
throw std::out_of_range("IRON token history exceeds the context length");
}
auto words = worker_.logits(tokens_);
buffer<bf16> output(words.size());
std::memcpy(output.data(), words.data(), words.size() * sizeof(words[0]));
return output;
}

CausalBackend::CausalBackend(const flm::backend::BackendContext& context,
std::string model)
: engine_(context.model_path, std::move(model),
ValidateContextLength(context.context_length)) {}

::causal_lm& CausalBackend::engine() { return engine_; }

std::string CausalBackend::id() const {
return flm::backend::kIronBackendId;
}

std::string CausalBackend::detail() const { return "IRON server"; }

bool CausalBackend::supports_preemption() const { return false; }

bool CausalBackend::poisoned() const noexcept { return engine_.poisoned(); }

flm::backend::BackendFactory causal_factory(std::string model) {
return [model = std::move(model)](
const flm::backend::BackendContext& context) {
return std::make_unique<CausalBackend>(context, model);
};
}

} // namespace flm::iron
30 changes: 30 additions & 0 deletions src/common/iron/embedding.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
// SPDX-License-Identifier: MIT

#include "iron/embedding.hpp"

#include <stdexcept>

namespace flm::iron {

void Embedding::load_model(std::string model_path, json,
bool enable_preemption) {
if (enable_preemption) {
throw std::invalid_argument(
"The IRON embedding backend does not support preemption");
}
worker_ = std::make_unique<Worker>(model_path, worker_model_);
is_model_loaded = true;
this->model_path = std::move(model_path);
}

std::vector<float> Embedding::embed(std::string& text,
embedding_task_type_t task_type) {
if (!worker_) {
throw std::runtime_error("The IRON embedding model is not loaded");
}
return worker_->embed(text, task_type == task_document ? "document" : "query",
dimensions_);
}

} // namespace flm::iron
Loading
Loading