Sunday, February 27, 2022

Using Zig as cross-platform C toolchain

Using Zig As Cross Platform C Toolchain

27 Feb 2022

I like to learn at least one new programming language every year - it’s mostly for fun - they are rarely useful for projects that I actually have to ship. This year, I’ve decided to look at Zig. After skimming through the official documentation and finishing the ray tracing in one weekend exercise, I realize that Zig can help with my game even though it has zero lines of Zig code.

A little background information. Most of the gameplay code is written in the game engine’s scripting environment in TypeScript. But there are a few native binaries that I need to ship as well. Given the game supports six platforms: Windows, Mac, Linux, Web, iOS, and Android, this process is quite tedious (although usually these binaries are only needed for a subset of platforms)

Currently, I do not use a cross-platform compilation toolchain - because they are hard to set up and have a lot of issues. Instead, I set up a toolchain for each target platform (I need it anyway to ship the game executable), compile the binary, put it in the repository, and call it a day. It’s not an elegant solution but it works - as long as I don’t update them very often.

Compile C with Zig

Zig has a decent cross compilation support. Not only that, it also provides a way to build C code: zig cc

Let’s try with an example project. Duktape is an embeddable JavaScript engine with a small footprint, written in C99. Since Windows is my main development environment, there are two choices: MSVC or MinGW(GCC/Clang). Each involves gigabytes of downloads and a complex installation process.

Compared to that, setting up Zig is simple: download the prebuild binary for Windows, extract in a directory, add zig executable to PATH and we are good to go.

Now we can try to compile the example that comes with DukTape. Here’s the command to do that in the official documentation:

gcc -std=c99 -o hello -Isrc src/duktape.c examples/hello/hello.c -lm

So we can replace gcc with zig cc (Note: I am using Git Bash/MinGW64 shell)

zig cc -std=c99 -o hello -Isrc src/duktape.c examples/hello/hello.c -lm

The command finishes without an error - that’s a good sign. Let’s try to run it

$ ./hello
Illegal instruction

Oops, that doesn’t look good. After some googling, it turns out that by default, zig will pass -fsanitize=undefined -fsanitize-trap=undefined to Clang, which will cause undefined behavior (UB) to generate “illegal instruction”. In the DukTape code, there are some UB but since our purpose is not to fix it (we have a game to ship!), let’s ignore it for now by adding -fno-sanitize=undefined

zig cc -std=c99 -o hello -Isrc src/duktape.c examples/hello/hello.c -lm -fno-sanitize=undefined

Now it looks better!

$ ./hello
Hello world!
2+3=5

This is very impressive considering all I need is a ~60MB self-contained Zig compiler on the Windows platform. But it’s not only that - zig cc also supports cross-compilation out of the box.

Cross Compilation

Let’s try compiling the same code on Windows for macOS. This can be achieved by simply adding a target

zig cc -std=c99 -o hello -Isrc src/duktape.c examples/hello/hello.c -lm -fno-sanitize=undefined -target x86_64-macos

Then copy the binary to my Macbook Pro (Intel), make it executable, and run it

% chmod +x ./hello
% ./hello
Hello world!
2+3=5

It just works!

Working With C in Zig

At this point, my game, which has zero line of Zig code, can already benefit from the wonderful toolchain that Zig comes with. But since we are here, let’s maybe convert some of the C code to Zig - after all Zig is a more modern language and is much more pleasant to use.

We can use zig init-exe to initialize an empty Zig project template. We will get a build script build.zig and a main source file src/main.zig. To use Duktape in this Zig project, let’s copy duktape.c duktape.h and duk_config.h to src/vendor. There are two main approaches to import C code: @cImport and zig translate-c. They both utilize the same underlying infrastructure but the latter offers more flexibility.

First, we need to translate duktape.h to Zig and write it to a file

zig translate-c -lc duktape.h > duktape.zig

Then we can simply import the Zig file in our main

const std = @import("std");
const duktape = @import("vendor/duktape.zig");
pub fn main() !void {
    const ctx = duktape.duk_create_heap_default();
    const code = duktape.duk_peval_string(ctx, "5+1");
    std.log.info("peval result code = {}", .{code});
    const result = duktape.duk_get_int(ctx, -1);
    std.log.info("peval result = {}", .{result});
}

Another benefit of zig translate-c is that we can get some nice IntelliSense support from IDE/Editor via Zig Language Server.

To run our project, we need to modify the build script. The default project template should have a section that looks like this:

const exe = b.addExecutable("duktape-zig", "src/main.zig");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.install();

We need to add the C source file and enable libc linking (we also need to pass -fno-sanitize=undefined to Clang as discussed above)

const flags = [_][]const u8 {
    "-fno-sanitize=undefined",
};
exe.addCSourceFile("src/vendor/duktape.c", &flags);
exe.linkLibC();

We can now run the project by using zig build run. However, if we do this, we will get an error:

$ zig build run
.\src\vendor\duktape.zig:3670:65: error: expected type '?fn(?*anyopaque, usize) callconv(.C) ?*anyopaque', found '?*anyopaque'
pub inline fn duk_create_heap_default() @TypeOf(duk_create_heap(NULL, NULL, NULL, NULL, NULL)) {

Let’s inspect the translated duktape.zig file:

pub const NULL = @import("std").zig.c_translation.cast(?*anyopaque, @as(c_int, 0));

pub inline fn duk_create_heap_default() @TypeOf(duk_create_heap(NULL, NULL, NULL, NULL, NULL)) {
    return duk_create_heap(NULL, NULL, NULL, NULL, NULL);
}

pub const duk_alloc_function = ?fn (?*anyopaque, duk_size_t) callconv(.C) ?*anyopaque;

pub extern fn duk_create_heap(alloc_func: duk_alloc_function, realloc_func: duk_realloc_function, free_func: duk_free_function, heap_udata: ?*anyopaque, fatal_handler: duk_fatal_function) ?*duk_context;

This looks complicated at first but after some digging, we can see that duk_create_heap accepts an optional function type, but NULL, which is used for bridging C’s NULL, cannot be coerced into that - thus the complaint from the compiler. There’s in fact an open issue about this. If we look at duktape.h source file, it looks like this

#define duk_create_heap_default() \
        duk_create_heap(NULL, NULL, NULL, NULL, NULL)

A simple solution to get our program to compile is to use Zig’s null instead of NULL:

pub inline fn duk_create_heap_default() @TypeOf(duk_create_heap(null, null, null, null, null)) {
    return duk_create_heap(null, null, null, null, null);
}

Now if we do zig build run, we will get

$ zig build run
info: peval result code = 0
info: peval result = 6

Conclusion

Traditionally, if I need to work with C files but want some batteries included, C++ is the language to go to. C++ has a large feature set and a heavy toolchain but most of the time, I only use a small subset of features. Plus C++ doesn’t spark joy.

Zig fills this niche nicely. It is easy to set up, has a modern toolchain and build system that works well on different platform. It comes with cross-compilation support out of the box. The language is easy to pick up and an absolute joy to work with. If you don’t need the sledgehammer that is C++, give Zig a try.



from Hacker News https://ift.tt/UsdZBt3

TorchRec, a library for modern production recommendation systems

by Meta AI - Donny Greenberg, Colin Taylor, Dmytro Ivchenko, Xing Liu

We are excited to announce TorchRec, a PyTorch domain library for Recommendation Systems. This new library provides common sparsity and parallelism primitives, enabling researchers to build state-of-the-art personalization models and deploy them in production.

How did we get here?

Recommendation Systems (RecSys) comprise a large footprint of production-deployed AI today, but you might not know it from looking at Github. Unlike areas like Vision and NLP, much of the ongoing innovation and development in RecSys is behind closed company doors. For academic researchers studying these techniques or companies building personalized user experiences, the field is far from democratized. Further, RecSys as an area is largely defined by learning models over sparse and/or sequential events, which has large overlaps with other areas of AI. Many of the techniques are transferable, particularly for scaling and distributed execution. A large portion of the global investment in AI is in developing these RecSys techniques, so cordoning them off blocks this investment from flowing into the broader AI field.

By mid-2020, the PyTorch team received a lot of feedback that there hasn’t been a large-scale production-quality recommender systems package in the open-source PyTorch ecosystem. While we were trying to find a good answer, a group of engineers at Meta wanted to contribute Meta’s production RecSys stack as a PyTorch domain library, with a strong commitment to growing an ecosystem around it. This seemed like a good idea that benefits researchers and companies across the RecSys domain. So, starting from Meta’s stack, we began modularizing and designing a fully-scalable codebase that is adaptable for diverse recommendation use-cases. Our goal was to extract the key building blocks from across Meta’s software stack to simultaneously enable creative exploration and scale. After nearly two years, a battery of benchmarks, migrations, and testing across Meta, we’re excited to finally embark on this journey together with the RecSys community. We want this package to open a dialogue and collaboration across the RecSys industry, starting with Meta as the first sizable contributor.

Introducing TorchRec

TorchRec includes a scalable low-level modeling foundation alongside rich batteries-included modules. We initially target “two-tower” ([1], [2]) architectures that have separate submodules to learn representations of candidate items and the query or context. Input signals can be a mix of floating point “dense” features or high-cardinality categorical “sparse” features that require large embedding tables to be trained. Efficient training of such architectures involves combining data parallelism that replicates the “dense” part of computation and model parallelism that partitions large embedding tables across many nodes.

In particular, the library includes:

  • Modeling primitives, such as embedding bags and jagged tensors, that enable easy authoring of large, performant multi-device/multi-node models using hybrid data-parallelism and model-parallelism.
  • Optimized RecSys kernels powered by FBGEMM , including support for sparse and quantized operations.
  • A sharder which can partition embedding tables with a variety of different strategies including data-parallel, table-wise, row-wise, table-wise-row-wise, and column-wise sharding.
  • A planner which can automatically generate optimized sharding plans for models.
  • Pipelining to overlap dataloading device transfer (copy to GPU), inter-device communications (input_dist), and computation (forward, backward) for increased performance.
  • GPU inference support.
  • Common modules for RecSys, such as models and public datasets (Criteo & Movielens).

To showcase the flexibility of this tooling, let’s look at the following code snippet, pulled from our DLRM Event Prediction example:

# Specify the sparse embedding layers
eb_configs = [
   EmbeddingBagConfig(
       name=f"t_{feature_name}",
       embedding_dim=64,
       num_embeddings=100_000,
       feature_names=[feature_name],
   )
   for feature_idx, feature_name in enumerate(DEFAULT_CAT_NAMES)
]

# Import and instantiate the model with the embedding configuration
# The "meta" device indicates lazy instantiation, with no memory allocated
train_model = DLRM(
   embedding_bag_collection=EmbeddingBagCollection(
       tables=eb_configs, device=torch.device("meta")
   ),
   dense_in_features=len(DEFAULT_INT_NAMES),
   dense_arch_layer_sizes=[512, 256, 64],
   over_arch_layer_sizes=[512, 512, 256, 1],
   dense_device=device,
)

# Distribute the model over many devices, just as one would with DDP.
model = DistributedModelParallel(
   module=train_model,
   device=device,
)

optimizer = torch.optim.SGD(params, lr=args.learning_rate)
# Optimize the model in a standard loop just as you would any other model!
# Or, you can use the pipeliner to synchronize communication and compute
for epoch in range(epochs):
   # Train

Scaling Performance

TorchRec has state-of-the-art infrastructure for scaled Recommendations AI, powering some of the largest models at Meta. It was used to train a 1.25 trillion parameter model, pushed to production in January, and a 3 trillion parameter model which will be in production soon. This should be a good indication that PyTorch is fully capable of the largest scale RecSys problems in industry. We’ve heard from many in the community that sharded embeddings are a pain point. TorchRec cleanly addresses that. Unfortunately it is challenging to provide large-scale benchmarks with public datasets, as most open-source benchmarks are too small to show performance at scale.

Looking ahead

Open-source and open-technology have universal benefits. Meta is seeding the PyTorch community with a state-of-the-art RecSys package, with the hope that many join in on building it forward, enabling new research and helping many companies. The team behind TorchRec plan to continue this program indefinitely, building up TorchRec to meet the needs of the RecSys community, to welcome new contributors, and to continue to power personalization at Meta. We’re excited to begin this journey and look forward to contributions, ideas, and feedback!

References

[1] Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations

[2] DLRM: An advanced, open source deep learning recommendation model



from Hacker News https://ift.tt/JWrYTVK

Depict.ai (YC S20) Is Hiring Engineers

Comments

from Hacker News https://ift.tt/ASoGc42

New JSON query operators in SQLite 3.38.0

SQLite 3.38.0 introduced improvements to JSON query syntax using -> and ->> operators that are similar to PostgreSQL JSON functions. In this post we will look into how this simplifies the query syntax.

Installation

The JSON functions are now built-ins. It is no longer necessary to use the -DSQLITE_ENABLE_JSON1 compile-time option to enable JSON support. JSON is on by default. Disable the JSON interface using the new -DSQLITE_OMIT_JSON compile-time option.

With the release of 3.38.0 JSON support is on by default. SQLite also provides pre-built binaries for Linux, Windows and Mac. For Linux you can get started with below

wget https://www.sqlite.org/2022/sqlite-tools-linux-x86-3380000.zip
unzip sqlite-tools-linux-x86-3380000.zip
cd sqlite-tools-linux-x86-3380000
rlwrap ./sqlite3

I found that support for readline is missing in the prebuilt binaries. If readline and other build utilities are installed in your machine you can download and compile SQLite binary.

wget https://www.sqlite.org/2022/sqlite-autoconf-3380000.tar.gz
tar -xvf sqlite-autoconf-3380000.tar.gz
cd sqlite-autoconf-3380000
./configure
make
./sqlite3

Sample data

In this post we will be using a sample data that stores the interests of a user as a JSON and see how the new operators provide support in querying. The table has an auto incrementing id as primary key with name as text and a JSON storing interests of the user. Let’s also assume the array of likes are stored in the order of preferences such that for John he likes skating the most and swimming as the last.

./sqlite3
SQLite version 3.38.0 2022-02-22 18:58:40
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> create table user(id integer primary key, name text, interests json);
sqlite> insert into user values(null, "John", '{"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]}');
sqlite> insert into user values(null, "Kate", '{"likes": ["reading", "swimming"], "dislikes": ["skating"]}');
sqlite> insert into user values(null, "Jim", '{"likes": ["reading", "swimming"], "dislikes": ["cooking"]}');
sqlite> .mode column
sqlite> select * from user;
id  name  interests                                                   
--  ----  ------------------------------------------------------------
1   John  {"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]}                                               
2   Kate  {"likes": ["reading", "swimming"], "dislikes": ["skating"]}
3   Jim   {"likes": ["reading", "swimming"], "dislikes": ["cooking"]}

Operator semantics

SQLite docs

With the above schema in place we can now use the JSON operators to query. With -> we can have the left part as a JSON component and the right part as a path expression. -> also provides a JSON representation in return so it can be chained in nested lookups. In the below example we look for users who have reading as their first preference. Since the interests column is of JSON type we can use -> operator along with $.likes which is a path expression to get the likes attribute. Then we can pass it to ->> which also takes a path expression to its right but returns value as SQLite datatype like text, integer etc. instead of JSON. Here $[0] is used to access the first element of the array. As per docs path also supports negative indexing where $[#-1] can be used to access last element of the array.

select id, name, interests from user 
where interests->'$.likes'->>'$[0]' = 'reading';

id  name  interests                                                  
--  ----  -----------------------------------------------------------
2   Kate  {"likes": ["reading", "swimming"], "dislikes": ["cooking"]}
3   Jim   {"likes": ["reading", "swimming"], "dislikes": ["cooking"]}

select id, name, interests from user 
where interests->'$.likes'->>'$[0]' = 'skating';

id  name  interests                                                   
--  ----  ------------------------------------------------------------
1   John  {"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]}

The above query can be further simplified removing $


select id, name, interests from user 
where interests->'likes'->>'[0]' = 'skating';

id  name  interests                                                   
--  ----  ------------------------------------------------------------
1   John  {"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]}

The catch with -> and ->> is that -> returns a JSON representation and it also expects the right side to be a JSON. This might lead to some confusion like below example where using ->[0] doesn’t return any value since we are comparing JSON representation with text value of “skating”

-- Returns no value

select id, name, interests from user 
where interests->'likes'->'[0]' = 'skating';

-- Returns value

select id, name, interests from user 
where interests->'likes'->'[0]' = '"skating"';

id  name  interests                                                   
--  ----  ------------------------------------------------------------
1   John  {"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]} 

-- Returns value too with the string represented as JSON

select id, name, interests from user 
where interests->'likes'->'[0]' = json_quote('skating');

id  name  interests                                                   
--  ----  ------------------------------------------------------------
1   John  {"likes": ["skating", "reading", "swimming"], "dislikes": ["cooking"]}

We can also the operators to get specific fields like queries.

-- List of first and last preference of users with skating as their first preference

select id, name, 
       interests->'likes'->>'[0]' as first_preference, 
       interests->'likes'->>'$[#-1]' as last_preference 
       from user where interests->'likes'->>'[0]' = 'skating';

id  name  first_preference  last_preference
--  ----  ----------------  ---------------
1   John  skating           swimming

We can also use the operators in sorting and grouping

-- List of users sorted by their first preference

select id, name, 
       interests->'likes'->>'[0]' as first_preference
       interests->'likes'->>'$[#-1]' as last_preference 
       from user order by interests->'likes'->>'[0]';

id  name  first_preference  last_preference
--  ----  ----------------  ---------------
2   Kate  reading           swimming     
3   Jim   reading           swimming     
1   John  skating           swimming

--  List of users grouped by their first preference

select id, name, 
       interests->'likes'->>'[0]' as first_preference, 
       interests->'likes'->>'$[#-1]' as last_preference 
       from user group by interests->'likes'->>'[0]';

id  name  first_preference  last_preference
--  ----  ----------------  ---------------
2   Kate  reading           swimming     
1   John  skating           swimming

Indexing

Index can also be created for a given expression thus making the query efficient. Once we create an index for querying by first preference the index is used for

-- Query plan without index

explain query plan select id, name, interests from user 
where interests->'likes'->>'[0]' = 'skating';
QUERY PLAN
--SCAN user


-- Create index on first preference of a user

create index idx_first_preference on user(interests->'likes'->>'[0]');

explain query plan select id, name, interests from user 
where interests->'likes'->>'[0]' = 'skating';
QUERY PLAN
--SEARCH user USING INDEX idx_first_preference (<expr>=?)

explain query plan select id, name, interests from user 
where interests->'likes'->>'[1]' = 'skating';
QUERY PLAN
--SCAN user

Conclusion

Enabling JSON by default and the new operators in 3.38.0 improve adoption and ergonomics of using JSON in SQLite. PostgreSQL has more rich query support which will be hopefully added in future releases.



from Hacker News https://ift.tt/rv97sdJ

Saturday, February 26, 2022

Singapore advises local firms to beef up cyberdefence amidst Ukraine conflict

Singapore has issued an advisory note highlighting the need for local organisations to bolster their cyberdefence amidst the ongoing conflict between Ukraine and Russia. In particular, businesses should be on the lookout for possible ransomware attacks as such tactics are commonly used by threat actors. 

There were no immediate reports of any threats to local businesses related to the Ukraine conflict, but organisations here were urged to take "active steps" to beef up their cybersecurity posture, according to Cyber Security Agency of Singapore (CSA). The government agency noted that cyber attacks on Ukraine and developments in the conflict had fuelled warnings of increased cyber threats across the globe. 

Organisations in Singapore should increase their vigilance and strengthen their cyberdefences to safeguard against potential attacks, such as web defacement, distributed denial of service (DDoS), and ransomware. 

In an advisory note issued Sunday, Singapore Computer Emergency Response Team (SingCERT) pointed to the need to keep watch for ransomware attacks, which were one of the most common attacks launched by threat actors. 

"Falling victim to such attacks will adversely impact the operations and business continuity of any organisation," said SingCERT, which sits within CSA. 

It said Singapore businesses should carry out necessary steps to secure their networks and review system logs to swiftly identify potential intrusions. These should include ensuring systems and applications were patched and updated to the latest version, disabling ports that were not essential for business purposes, and adopting strong access controls when using cloud services. 

In addition, system events should be properly logged to facilitate investigation of suspicious issues while both inbound and outbound network traffic should be monitored for suspicious communications or data transmissions, SingCERT said. 

It added that organisations also should have in place incident response and business continuity plans. Any suspicious compromise of corporate networks or evidence of such incidents should be reported to SingCERT

The Ukraine government reportedly had sought volunteers from the nation's hacker community to protect critical infrastructure and run cyber spying missions against Russia. Citing sources involved in the call to action, a Reuters report said requests for volunteers popped up on hacker forums on Thursday. 

RELATED COVERAGE



from Latest Topic for ZDNet in... https://ift.tt/YjBPME6

Ask HN: The book that did it for you in Math and/or CS

Comments

from Hacker News https://ift.tt/gkrQlsI

An Ableton Live Set is gzipped XML + a Ruby gem (2012)

Recently I started using git and github.com for version control of my Ableton Live Sets.  I pushed  these files to github to have backups and to be able to rollback to a previous version in case I got carried away on some ill-fated musical tangent.

But I was missing out on a one of the nicest features of git and github, diffs: I could not see the differences between two files or versions of my Live Sets.  Why?

Ableton's Live Set file is binary, not line based text.  I decided to reverse engineer the file:

  $ file Amy.als
  Amy.als: gzip compressed data, from Unix

Hmm.... Then, of course, I tried this:

  $ cp Amy.als Amy.gz
  $ gunzip Amy.gz 
  $ file Amy
  Amy: XML  document text

What!?

Reverse engineering done. Wait. Will Ableton Live read the file if I just gzip it and rename it?

  $ mv Amy.gz AmyTest.als
  $ open AmyTest.als

Poof! There it is. This was very nice of the Ableton developers. Thanks guys.  Now I can add and commit XML files to git and not binary.  It sucks to have to do this by hand for each Live Set each time I want to make a commit so I created the Ruby gem guard-live-set.

From the guard documentation: Guard is a command line tool to easily handle events on file system modifications.  My guard specialization just watches for any changes to a .als file and immediately creates a .als.xml version of it.  There you go, enjoy.

My hope is for more people to start committing their creations so that others can collaborate and then we can all benefit and improve from each other's creations.  I don't need to explain how successful open source software has been, why not open source music?  I'll throw in my Live-Set-github hat in with this: https://github.com/mgarriss/ugly.live.sets.

There is still a lot more work that needs to be done before true barrier free Live Set collaboration can be a reality.  First is the sharing of samples and plugins.  These are large files and not suitable for github.com.  Next we probably need some kind of .als lint program that could do more than indent; maybe it could keep things in a well defined order which would make the diffs cleaner.  An online friend is putting some thought into a Ruby gem for editing these .als files and I've agreed to contribute to that so stay tuned...



from Hacker News https://ift.tt/GXytDRv