Skip to content

Commit f98b8ac

Browse files
authored
Merge pull request #25 from t81dev/phase-16-17-hardening-dx-5543509501038504867
Production Hardening & Developer Experience (Phases 16 & 17)
2 parents 25bb7a9 + 3061b92 commit f98b8ac

12 files changed

Lines changed: 318 additions & 29 deletions

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ OBJ_DIR = obj_dir
3939
VERILATOR_FLAGS = --cc $(HW_DIR)/ternary_fabric_top.v -I$(HW_DIR) --Mdir $(OBJ_DIR) -Wno-fatal
4040

4141
# Targets
42-
ALL_C_BINS = $(BIN_DIR)/mediator_mock $(BIN_DIR)/pt5_example $(BIN_DIR)/reference_tfmbs $(BIN_DIR)/test_device $(BIN_DIR)/mock_llama $(BIN_DIR)/test_dynamic_detection $(BIN_DIR)/test_phase10 $(BIN_DIR)/test_multi_tile
42+
ALL_C_BINS = $(BIN_DIR)/mediator_mock $(BIN_DIR)/pt5_example $(BIN_DIR)/reference_tfmbs $(BIN_DIR)/test_device $(BIN_DIR)/mock_llama $(BIN_DIR)/test_dynamic_detection $(BIN_DIR)/test_phase10 $(BIN_DIR)/test_multi_tile $(BIN_DIR)/test_eviction
4343
ALL_LIBS = $(BIN_DIR)/libtfmbs_device$(SHLIB_EXT) $(BIN_DIR)/libtfmbs_intercept$(SHLIB_EXT)
4444
ALL_HW_SIM = $(BIN_DIR)/fabric_tb.vvp
4545

@@ -82,6 +82,9 @@ $(BIN_DIR)/test_phase10: tests/test_phase10.c $(SRC_DIR)/tfmbs_driver_mock.c $(S
8282
$(BIN_DIR)/test_multi_tile: tests/test_multi_tile.c $(BIN_DIR)/libtfmbs_device$(SHLIB_EXT)
8383
$(CC) $(CFLAGS) -o $@ $< -L$(BIN_DIR) -ltfmbs_device
8484

85+
$(BIN_DIR)/test_eviction: tests/test_eviction.c $(BIN_DIR)/libtfmbs_device$(SHLIB_EXT)
86+
$(CC) $(CFLAGS) -o $@ $< -L$(BIN_DIR) -ltfmbs_device
87+
8588
run_mock_llama: $(BIN_DIR)/mock_llama $(ALL_LIBS)
8689
export FABRIC_SHORT_CIRCUIT=1; \
8790
$(LIBPATH_ENV)=$(BIN_DIR) $(PRELOAD_ENV)=$(BIN_DIR)/libtfmbs_intercept$(SHLIB_EXT) ./$(BIN_DIR)/mock_llama

USER_MANUAL.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,28 @@ Welcome to the **Ternary Fabric** user manual. This documentation is designed to
4040
The Ternary Fabric can accelerate applications like `llama.cpp` without source code modifications using the `libtfmbs_intercept.so` interposer.
4141

4242
### Usage
43+
The easiest way to run an application with Fabric acceleration is using the `tfmbs-run` tool:
4344
```bash
44-
# Enable Fabric acceleration with CPU short-circuiting
45-
export FABRIC_SHORT_CIRCUIT=1
46-
LD_PRELOAD=./libtfmbs_intercept.so ./my_app
45+
./tools/tfmbs-run ./my_app
4746
```
4847

4948
### Environment Variables
5049
* `FABRIC_SHORT_CIRCUIT=1`: Enables the interposer to bypass CPU compute loops once weights are resident in the Fabric.
51-
* `TFMBS_DEBUG=1`: Enables verbose logging of memory allocations and offloading events.
50+
* `TFMBS_DEBUG=1`: Enables detailed real-time logging of memory registry changes, GEMV offloads, and async waits.
51+
* `TFMBS_VALIDATE=1`: Bit-exact comparison of Fabric results against a CPU reference for every operation.
52+
53+
### Cooperative Mode (Explicit Registration)
54+
If you want to avoid the overhead of the "First Scan" heuristic, you can explicitly register weight buffers by calling `fabric_register_weight(ptr, size)` from your application.
5255

5356
---
5457

58+
## ⚠️ Best Practices & Limitations
59+
60+
### When NOT to use Fabric
61+
- **Small Matrices:** GEMV operations smaller than 1024 elements may incur more overhead than they save.
62+
- **High-Precision Requirements:** Fabric is optimized for ternary weights. High-precision (FP32/BF16) weights will be quantized/compressed, which may impact accuracy.
63+
- **Frequent Writes:** If you frequently write to weight buffers, the interposer will constantly need to re-pack them into PT-5 format.
64+
5565
## 📊 Interpreting Telemetry
5666

5767
When running with the interposer or Python API, the Fabric provides real-time telemetry:

docs/16_PRODUCTION_HARDENING.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Phase 16: Production Hardening
2+
3+
This phase focuses on making the TFMBS "Illusion" stable, reliable, and safe for production-like environments.
4+
5+
## 🛡️ Robust Signal Handling
6+
7+
The TFMBS interposer uses `SIGSEGV` to intercept memory accesses and redirect them to the Fabric. Previously, any unmanaged segfault would cause an immediate process exit.
8+
9+
- **Handler Delegation:** The interposer now saves the previous signal handler (`struct sigaction`) and delegates to it if a faulting address is not found in the TFMBS registry.
10+
- **Proper Crashes:** Real segmentation faults in the host application now behave normally (causing a crash with a core dump) rather than being swallowed or causing a mysterious exit.
11+
12+
## 💾 Memory & Deadlock Safety
13+
14+
- **OOM Awareness:** The Fabric emulator (`emu_fabric_alloc`) now correctly handles out-of-memory situations where all blocks are "busy" (pinned by active asynchronous tasks).
15+
- **Graceful Fallback:** If Fabric memory cannot be allocated, the interposer transparently falls back to host memory (via standard `malloc`).
16+
- **Busy Pinning:** Tasks in the asynchronous pipeline pin their associated memory blocks, preventing the LRU evictor from removing data that is currently being used by a hardware kernel.
17+
18+
## 🤝 Cooperative Mode (Explicit API)
19+
20+
While the transparent interposer is the primary interface, some applications may prefer explicit control to reduce overhead or improve reliability.
21+
22+
- **`fabric_register_weight(ptr, size)`**: Explicitly marks a host memory buffer as a weight buffer for the Fabric. This bypasses the heuristic "First Scan" phase and immediately establishes residency.
23+
24+
## 🛠️ Error Propagation
25+
26+
- **Status Codes:** `fabric_wait` and other internal APIs have been updated to return integer status codes instead of `void`. This allows the interposer to detect kernel failures and potentially fall back to CPU compute if a hardware error occurs.

docs/17_DEV_EXPERIENCE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Phase 17: Developer Experience (DX)
2+
3+
Phase 17 improves the tools and visibility available to developers working with TFMBS.
4+
5+
## 🔍 Debugging & Visualization
6+
7+
- **`TFMBS_DEBUG=1`**: Setting this environment variable enables detailed real-time logging in the interposer. It logs:
8+
- Memory registry additions.
9+
- GEMV offload triggers and inferred dimensions.
10+
- Asynchronous wait events and short-circuit jumps.
11+
- **`TFMBS_VALIDATE=1`**: Enabling validation mode causes the interposer to run a bit-exact reference CPU implementation of every GEMV task immediately after it completes on the Fabric. It compares the results and logs any mismatches.
12+
13+
## 🚀 CLI Tools
14+
15+
- **`tfmbs-run`**: A wrapper script located in `tools/` that automatically configures `LD_PRELOAD` and `LD_LIBRARY_PATH`.
16+
- Usage: `./tools/tfmbs-run ./my_app`
17+
- **`benchmark_performance.sh`**: A tool to compare the wall-clock performance of the Fabric against a pure CPU baseline using the `mock_llama` benchmark.
18+
19+
## 📈 Performance Profiling
20+
21+
- **Wall-Clock Metrics:** We've shifted focus from "Peak GOPS" to "End-to-End Time", providing a more realistic view of the system's impact on inference latency.

include/tfmbs_device.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ int fabric_memcpy_to(void* dest_fabric, const void* src_host, size_t size, int p
2727
int fabric_memcpy_from(void* dest_host, const void* src_fabric, size_t size, int unpack_pt5);
2828
int fabric_exec_gemv(void* weight_ptr, void* input_ptr, void* output_ptr, int rows, int cols);
2929

30+
// Cooperative API (Phase 16)
31+
void fabric_register_weight(void* ptr, size_t size);
32+
3033
// Async API (Phase 8)
3134
fabric_handle_t fabric_exec_gemv_async(void* weight_ptr, void* input_ptr, void* output_ptr, int rows, int cols);
32-
void fabric_wait(fabric_handle_t handle);
35+
int fabric_wait(fabric_handle_t handle);
3336

3437
// Low-level TFD submission (Phase 10)
3538
int fabric_submit_tfd(tfmbs_tfd_t* tfd);

src/fabric_emulator.c

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,33 @@ void* emu_fabric_alloc(size_t size) {
179179
}
180180

181181
// No block found, try evicting
182-
int any_used = 0;
182+
int any_evictable = 0;
183183
curr = g_blocks;
184-
while(curr) { if(curr->used && curr->busy_count == 0) any_used=1; curr=curr->next; }
184+
while(curr) { if(curr->used && curr->busy_count == 0) any_evictable=1; curr=curr->next; }
185185

186-
if (!any_used) {
187-
fprintf(stderr, "[TFMBS-Device] Out of Fabric Memory even after full eviction! Requested %zu\n", aligned_size);
186+
if (!any_evictable) {
187+
// Check if there are ANY free blocks (even if too small)
188+
int any_free = 0;
189+
curr = g_blocks;
190+
while(curr) { if(!curr->used) any_free=1; curr=curr->next; }
191+
192+
if (!any_free) {
193+
fprintf(stderr, "[TFMBS-Device] Out of Fabric Memory: All blocks are BUSY. Requested %zu\n", aligned_size);
194+
pthread_mutex_unlock(&g_fabric_mutex);
195+
return NULL;
196+
}
197+
}
198+
199+
if (any_evictable) {
200+
evict_lru();
201+
} else {
202+
// We have some free blocks but they are not large enough or fragmented
203+
// and we can't evict anything else.
204+
fprintf(stderr, "[TFMBS-Device] Out of Fabric Memory: Fragmentation or Busy blocks. Requested %zu\n", aligned_size);
188205
pthread_mutex_unlock(&g_fabric_mutex);
189206
return NULL;
190207
}
191208

192-
evict_lru();
193-
194209
// Coalesce free blocks
195210
curr = g_blocks;
196211
while (curr && curr->next) {
@@ -463,8 +478,8 @@ fabric_handle_t emu_fabric_exec_gemv_async(void* weight_ptr, void* input_ptr, vo
463478
return (fabric_handle_t)task;
464479
}
465480

466-
void emu_fabric_wait(fabric_handle_t handle) {
467-
if (!handle) return;
481+
int emu_fabric_wait(fabric_handle_t handle) {
482+
if (!handle) return -1;
468483
fabric_task_t* task = (fabric_task_t*)handle;
469484

470485
pthread_mutex_lock(&task->mutex);
@@ -476,6 +491,7 @@ void emu_fabric_wait(fabric_handle_t handle) {
476491
pthread_mutex_destroy(&task->mutex);
477492
pthread_cond_destroy(&task->cond);
478493
free(task);
494+
return 0;
479495
}
480496

481497
static void* fabric_worker_loop(void* arg) {

src/fabric_emulator.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ int emu_fabric_memcpy_to(void* dest_fabric, const void* src_host, size_t size, i
1010
int emu_fabric_memcpy_from(void* dest_host, const void* src_fabric, size_t size, int unpack_pt5);
1111
int emu_fabric_exec_gemv(void* weight_ptr, void* input_ptr, void* output_ptr, int rows, int cols, uint8_t tile_mask);
1212
fabric_handle_t emu_fabric_exec_gemv_async(void* weight_ptr, void* input_ptr, void* output_ptr, int rows, int cols, uint8_t tile_mask);
13-
void emu_fabric_wait(fabric_handle_t handle);
13+
int emu_fabric_wait(fabric_handle_t handle);
1414
void emu_fabric_get_metrics(fabric_metrics_t* out_metrics);
1515
int emu_is_fabric_ptr(const void* ptr);
1616

src/libtfmbs_device.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,13 @@ fabric_handle_t fabric_exec_gemv_async(void* weight_ptr, void* input_ptr, void*
105105
return emu_fabric_exec_gemv_async(weight_ptr, input_ptr, output_ptr, rows, cols, tile_mask);
106106
}
107107

108-
void fabric_wait(fabric_handle_t handle) {
108+
int fabric_wait(fabric_handle_t handle) {
109109
init_device();
110110
if (g_tfmbs_fd >= 0) {
111111
tfmbs_ioc_wait_t args = { .handle = (uint64_t)handle };
112-
tfmbs_dev_ioctl(g_tfmbs_fd, TFMBS_IOC_WAIT, &args);
113-
return;
112+
return tfmbs_dev_ioctl(g_tfmbs_fd, TFMBS_IOC_WAIT, &args);
114113
}
115-
emu_fabric_wait(handle);
114+
return emu_fabric_wait(handle);
116115
}
117116

118117
int fabric_submit_tfd(tfmbs_tfd_t* tfd) {

src/libtfmbs_intercept.c

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ static void* (*real_mmap)(void*, size_t, int, int, int, off_t) = NULL;
3838
static __thread int in_interposer = 0;
3939
static int initializing = 0;
4040
static int g_short_circuit_enabled = 0;
41+
static int g_debug_enabled = 0;
42+
static int g_validate_enabled = 0;
43+
static struct sigaction g_old_sa;
4144

4245
#define FABRIC_THRESHOLD (1024)
4346
#define MAX_ALLOCS 1024
@@ -47,6 +50,9 @@ typedef struct {
4750
void* ptr; size_t size; void* pt5_ptr; fabric_state_t state;
4851
size_t pages_touched;
4952
fabric_handle_t pending_handle;
53+
int pending_rows, pending_cols;
54+
void* pending_weight_ptr;
55+
void* pending_input_ptr;
5056
unsigned long last_read_seq;
5157
unsigned long last_write_seq;
5258
} fabric_metadata_t;
@@ -56,6 +62,7 @@ static int g_num_allocs = 0;
5662
static unsigned long g_access_seq = 0;
5763
static pthread_mutex_t g_reg_mutex = PTHREAD_MUTEX_INITIALIZER;
5864
static void* g_scratch_packed_in = NULL;
65+
static void* g_scratch_validate_out = NULL;
5966

6067
static fabric_metadata_t* find_meta(const void* ptr) {
6168
for (int i = 0; i < g_num_allocs; i++) {
@@ -102,8 +109,48 @@ static void sigsegv_handler(int sig, siginfo_t* si, void* unused) {
102109
if (m->pending_handle) {
103110
fabric_handle_t h = m->pending_handle;
104111
m->pending_handle = NULL;
105-
safe_log("[TFMBS] Waiting for pending async GEMV on %p\n", m->ptr);
106-
fabric_wait(h);
112+
if (g_debug_enabled) safe_log("[TFMBS-Debug] Waiting for pending async GEMV on %p\n", m->ptr);
113+
int status = fabric_wait(h);
114+
if (status != 0) safe_log("[TFMBS] Warning: fabric_wait returned %d\n", status);
115+
116+
if (g_validate_enabled && m->pending_weight_ptr && m->pending_input_ptr) {
117+
safe_log("[TFMBS-Validate] Validating Fabric output for %p...\n", m->ptr);
118+
mprotect(m->ptr, m->size, PROT_READ);
119+
int saved_ii = in_interposer; in_interposer = 1;
120+
121+
fabric_metadata_t* wm = find_meta(m->pending_weight_ptr);
122+
fabric_metadata_t* im = find_meta(m->pending_input_ptr);
123+
124+
if (wm && im) {
125+
int32_t* ref_out = (int32_t*)g_scratch_validate_out;
126+
if (mprotect(wm->ptr, wm->size, PROT_READ) != 0) safe_log("[TFMBS-Validate] mprotect weight failed: %s\n", strerror(errno));
127+
if (mprotect(im->ptr, im->size, PROT_READ) != 0) safe_log("[TFMBS-Validate] mprotect input failed: %s\n", strerror(errno));
128+
129+
for (int r = 0; r < m->pending_rows; r++) {
130+
int32_t acc = 0;
131+
for (int c = 0; c < m->pending_cols; c++) {
132+
acc += (int32_t)((int8_t*)wm->ptr)[r * m->pending_cols + c] *
133+
(int32_t)((int8_t*)im->ptr)[c];
134+
}
135+
ref_out[r] = acc;
136+
}
137+
138+
int errors = 0;
139+
for (int r = 0; r < m->pending_rows; r++) {
140+
if (((int32_t*)m->ptr)[r] != ref_out[r]) {
141+
if (errors < 5) safe_log("[TFMBS-Validate] Mismatch at row %d: Fabric=%d, CPU=%d\n", r, ((int32_t*)m->ptr)[r], ref_out[r]);
142+
errors++;
143+
}
144+
}
145+
if (errors == 0) safe_log("[TFMBS-Validate] ⭐ MATCH verified for %p\n", m->ptr);
146+
else safe_log("[TFMBS-Validate] ❌ ERROR: %d mismatches found in %p\n", errors, m->ptr);
147+
148+
mprotect(wm->ptr, wm->size, PROT_NONE);
149+
mprotect(im->ptr, im->size, PROT_READ | PROT_WRITE);
150+
}
151+
in_interposer = saved_ii;
152+
}
153+
107154
mprotect(m->ptr, m->size, PROT_READ | PROT_WRITE);
108155
in_interposer = saved; return;
109156
}
@@ -154,7 +201,6 @@ static void sigsegv_handler(int sig, siginfo_t* si, void* unused) {
154201
if (rows > 4096) rows = 4096; // Increased safety caps
155202
if (cols > 4096) cols = 4096;
156203

157-
if (!g_scratch_packed_in) g_scratch_packed_in = fabric_alloc(4096*4096);
158204
safe_log("[TFMBS] Offloading GEMV (Dynamic): W=%p, I=%p, O=%p (%dx%d)\n", m->ptr, in_buf->ptr, out_buf->ptr, rows, cols);
159205

160206
mprotect(in_buf->ptr, in_buf->size, PROT_READ);
@@ -163,6 +209,12 @@ static void sigsegv_handler(int sig, siginfo_t* si, void* unused) {
163209
// Set output buffer to PROT_NONE and mark as pending
164210
mprotect(out_buf->ptr, out_buf->size, PROT_NONE);
165211
out_buf->pending_handle = fabric_exec_gemv_async(m->pt5_ptr, g_scratch_packed_in, out_buf->ptr, rows, cols);
212+
out_buf->pending_rows = rows;
213+
out_buf->pending_cols = cols;
214+
out_buf->pending_weight_ptr = m->ptr;
215+
out_buf->pending_input_ptr = in_buf->ptr;
216+
217+
if (g_debug_enabled) safe_log("[TFMBS-Debug] Offload Triggered: W=%p, I=%p, O=%p (%dx%d)\n", m->ptr, in_buf->ptr, out_buf->ptr, rows, cols);
166218

167219
if (g_short_circuit_enabled) {
168220
ucontext_t* uc = (ucontext_t*)unused;
@@ -198,38 +250,69 @@ static void sigsegv_handler(int sig, siginfo_t* si, void* unused) {
198250
}
199251
in_interposer = saved; return;
200252
}
201-
in_interposer = saved; _exit(1);
253+
254+
// Address not in registry: Fallback to previous handler
255+
if (g_old_sa.sa_flags & SA_SIGINFO) {
256+
g_old_sa.sa_sigaction(sig, si, unused);
257+
} else if (g_old_sa.sa_handler == SIG_DFL) {
258+
// Reset to default and return, allowing instruction to re-trigger fault
259+
sigaction(SIGSEGV, &g_old_sa, NULL);
260+
} else if (g_old_sa.sa_handler != SIG_IGN) {
261+
g_old_sa.sa_handler(sig);
262+
}
263+
in_interposer = saved;
202264
}
203265

204-
static void reg_alloc(void* ptr, size_t size) {
266+
static void reg_alloc_internal(void* ptr, size_t size, fabric_state_t initial_state) {
267+
if (g_debug_enabled) safe_log("[TFMBS-Debug] Registry Add: %p (%zu bytes, state=%d)\n", ptr, size, (int)initial_state);
205268
pthread_mutex_lock(&g_reg_mutex);
206269
if (g_num_allocs < MAX_ALLOCS) {
207270
g_registry[g_num_allocs].ptr = ptr; g_registry[g_num_allocs].size = size;
208-
g_registry[g_num_allocs].pt5_ptr = NULL; g_registry[g_num_allocs].state = STATE_RAW;
271+
g_registry[g_num_allocs].pt5_ptr = NULL; g_registry[g_num_allocs].state = initial_state;
209272
g_registry[g_num_allocs].pages_touched = 0;
210273
g_registry[g_num_allocs].pending_handle = NULL;
211274
g_registry[g_num_allocs].last_read_seq = 0;
212275
g_registry[g_num_allocs].last_write_seq = 0;
213-
mprotect(ptr, size, PROT_NONE);
214276
g_num_allocs++;
215277
}
216278
pthread_mutex_unlock(&g_reg_mutex);
217279
}
218280

219281
static void init() __attribute__((constructor));
282+
static void init();
283+
284+
static void reg_alloc(void* ptr, size_t size) {
285+
reg_alloc_internal(ptr, size, STATE_RAW);
286+
mprotect(ptr, size, PROT_NONE);
287+
}
288+
289+
void fabric_register_weight(void* ptr, size_t size) {
290+
init();
291+
safe_log("[TFMBS] Explicit weight registration: %p (%zu bytes)\n", ptr, size);
292+
reg_alloc_internal(ptr, size, STATE_READY_TO_PACK);
293+
mprotect(ptr, size, PROT_NONE);
294+
}
220295
static void init() {
221296
if (real_malloc || initializing) return;
222297
initializing = 1;
223298
const char* sc = getenv("FABRIC_SHORT_CIRCUIT");
224299
if (sc && sc[0] == '1') g_short_circuit_enabled = 1;
300+
const char* dbg = getenv("TFMBS_DEBUG");
301+
if (dbg && dbg[0] == '1') g_debug_enabled = 1;
302+
const char* val = getenv("TFMBS_VALIDATE");
303+
if (val && val[0] == '1') g_validate_enabled = 1;
304+
225305
struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask);
226-
sa.sa_sigaction = sigsegv_handler; sigaction(SIGSEGV, &sa, NULL);
306+
sa.sa_sigaction = sigsegv_handler;
307+
sigaction(SIGSEGV, &sa, &g_old_sa);
227308
real_malloc = dlsym(RTLD_NEXT, "malloc");
228309
real_free = dlsym(RTLD_NEXT, "free");
229310
real_realloc = dlsym(RTLD_NEXT, "realloc");
230311
real_memcpy = dlsym(RTLD_NEXT, "memcpy");
231312
real_memset = dlsym(RTLD_NEXT, "memset");
232313
real_mmap = dlsym(RTLD_NEXT, "mmap");
314+
g_scratch_packed_in = fabric_alloc(4096*4096);
315+
g_scratch_validate_out = fabric_alloc(4096*4096);
233316
initializing = 0;
234317
}
235318

0 commit comments

Comments
 (0)